unit-testingautofixturestub-data-generationsemantic-comparison

Trouble using Autofixture's CreateProxy to use Likeness, SemanticComparison features


In an earlier question I asked about Autofixture's CreateProxy method, a potential bug was identified.

I don't think this failing test is as a result of that, but rather my continued confusion about how the Likeness.Without(...).CreateProxy() syntax works. Consider the following failing test in which I make the original test ever so slightly more complex by creating a new instance of the object, considering its creation to be the SUT:

[Fact]
public void Equality_Behaves_As_Expected()
{
    // arrange: intent -> use the fixture-created Band as Object Mother
    var template = new Fixture().Create<Band>();

    // act: intent -> instantiated Band *is* the SUT
    var createdBand = new Band {Brass = template.Brass,
                                Strings = template.Brass};

    //   intent -> specify that .Brass should not be considered in comparison 
    var likeness = template.AsSource().OfLikeness<Band>().
        Without(x => x.Brass).CreateProxy(); // Ignore .Brass property

    // per [https://stackoverflow.com/a/15476108/533958] explicity assign
    // properties to likeness
    likeness.Strings = template.Strings;
    likeness.Brass = "foo"; // should be ignored

    // assert: intent -> check equality between created Band & template Band
    //         to include all members not excluded in likeness definition
    likeness.Should().Be(createdBand);          // Fails
    likeness.ShouldBeEquivalentTo(createdBand); // Fails
    Assert.True(likeness.Equals(createdBand));  // Fails
}

Here's the Band:

public class Band
{
    public string Strings { get; set; }
    public string Brass { get; set; }
}

My earlier question wasn't sufficiently complex to help me understand what the Source of the Likeness should be in general.

Should the source be the output of the SUT, in which case it would be compared to the template instance created by AutoFixture?

Or should the source be the template instance created by AutoFixture, in which case it would be compared to the output of the SUT?

EDIT: Corrected an error in the test

I realized that I had incorrectly assigned the template.Brass property to both the Brass and the Strings property of the new Band instance. The updated test reflects the correction with var createdBand = new Band {Brass = template.Brass, Strings = template.Strings} and all six assertions pass now.

[Fact]
public void Equality_Behaves_As_Expected()
{
    // arrange: intent -> use the fixture-created Band as Object Mother
    var template = new Fixture().Create<Band>();

    // act: intent -> instantiated Band *is* the SUT
    var createdBand = new Band {Brass = template.Brass, Strings = template.Strings};

    // likeness of created
    var createdLikeness = createdBand.AsSource().OfLikeness<Band>().
        Without(x => x.Brass).CreateProxy(); // .Brass should not be considered in comparison 

    // https://stackoverflow.com/a/15476108/533958 (explicity assign properties to likeness)
    createdLikeness.Strings = createdBand.Strings;
    createdLikeness.Brass = "foo"; // should be ignored

    // likeness of template
    var templateLikeness = template.AsSource().OfLikeness<Band>()
        .Without(x => x.Brass)
        .CreateProxy();
    templateLikeness.Strings = template.Strings;
    templateLikeness.Brass = "foo";

    // assert: intent -> compare created Band to template Band
    createdLikeness.Should().Be(template);
    createdLikeness.ShouldBeEquivalentTo(template);
    Assert.True(createdLikeness.Equals(template));

    templateLikeness.Should().Be(createdBand);
    templateLikeness.ShouldBeEquivalentTo(createdBand);
    Assert.True(templateLikeness.Equals(createdBand));
}

Solution

  • What you mean is:

    likeness.Should().BeAssignableTo<Band>(); // Returns true.
    

    In the example provided, the proxy generated from Likeness is a type deriving from Band, overriding Equals using the Semantic Comparison algorithm.

    Using Reflection that is:

    createdBand.GetType().IsAssignableFrom(likeness.GetType()) // Returns true.
    

    Update:

    The createBand and template instances are not affected by the CreateProxy method. Why they should?

    With Likeness CreateProxy you basically create a Custom Equality Assertion that allows you to do:

    Assert.True(likeness.Equals(createdBand)); // Passed. 
    

    Without it, the original Equality Assertion would fail:

    Assert.True(template.Equals(createdBand)); // Failed.
    

    However, the following will also fail:

    Assert.True(likeness.Equals(template));
    

    It fails because the Strings value is the one from the createdBand instance.

    This behavior is expected, and you can verify it using Likeness directly:

     createdBand.AsSource().OfLikeness<Band>()
         .Without(x => x.Brass).ShouldEqual(template);
    

    Output:

     The provided value `Band` did not match the expected value `Band`. The following members did not match:
          - Strings.