7

I'm trying to understand how to use the CreateProxy() feature of Likeness<T>() using two instances of a simple class.

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

With the following test, I use a Fixture to Create<T> a Band instance with values for the two string properties.

[Fact]
public void Equality_Behaves_As_Expected()
{
    // arrange
    var fixture = new Fixture();
    fixture.Customize(new AutoMoqCustomization());

    var original = fixture.Create<Band>();
    //   Brass something like --> "Brass65756b89-d9f3-42f8-88fc-ab6de5ae65cd"
    //   Strings something like --> "Strings7439fa1b-014d-4544-8428-baea66858940"

    // act
    var dupe = new Band {Brass = original.Brass, 
                         Strings = original.Strings};
    //   Brass same as original's like --> "Brass65756b89-d9f3-42f8-88fc-ab6de5ae65cd"
    //   Strings same as original's like --> "Strings7439fa1b-014d-4544-8428-baea66858940"

I've tried many different assertions, but the crux of the matter seems to be that the CreateProxy method is not populating the properties of Band, so that even when I try to compare two instances of Band with the same property values, the instance from the CreateProxy method always has null values.

    // assert
    var likeness = dupe.AsSource().OfLikeness<Band>()
                       .Without(x => x.Brass).CreateProxy();
    //   Brass & String properties are null using dupe as source of likeness (!)

    //var likeness = original.AsSource().OfLikeness<Band>()
    //                       .Without(x => x.Brass).CreateProxy();
    //   Brass & String properties are null using original as source of likeness (!)

    //Assert.True(likeness.Equals(original)); // Fails
    //Assert.True(original.Equals(likeness)); // Fails

    // below are using FluentAssertions assembly
    //likeness.Should().Be(original);           // Fails (null properties)
    //original.Should().Be(likeness);           // Fails (null properties)
    //likeness.ShouldBeEquivalentTo(original);  // Fails (null properties)
    //original.ShouldBeEquivalentTo(likeness);  // Fails (null properties)
}

I've gotta be doing something wrong, but I've read everything I can find on the Ploeh blog and SO, and can't find an example suitably simple enough to compare to what I'm doing. Any ideas?

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
Jeff
  • 2,191
  • 4
  • 30
  • 49

1 Answers1

5

If you assign the values on the proxied instance (after calling the CreateProxy method) the test passes:

[Fact]
public void Equality_Behaves_As_Expected()
{
    // AutoMoqCustomization is not necessary.
    var original = new Fixture().Create<Band>();

    var likeness = original
        .AsSource()
        .OfLikeness<Band>()
        .Without(x => x.Brass)
        .CreateProxy();

    likeness.Brass = "foo"; // Ignored.
    likeness.Strings = original.Strings;

    Assert.True(likeness.Equals(original));
    likeness.Should().Be(original);
    likeness.ShouldBeEquivalentTo(original);
}

Keep in mind that Likeness creates a proxy on the target type and only that type's instance overrides Equals.

Since the source type remains intact, the following assertions will not succeed:

Assert.True(original.Equals(likeness));
original.Should().Be(likeness);
original.ShouldBeEquivalentTo(likeness);

Update

From version 3.0.4 and above the values are automatically copied to the proxy instance (which means, likeness.Strings = original.Strings; is going to happen automatically).

Nikos Baxevanis
  • 10,868
  • 2
  • 46
  • 80
  • But why does `Assert.True(likeness.Equals(original));` fail? The OP states that this line of code also fails. Shouldn't that pass? – Mark Seemann Mar 18 '13 at 17:37
  • Because the values were not assigned on the proxied instance. `Assert.True(likeness.Equals(original));` in the original test could pass if `CreateProxy` copied all properties from the source instance to the target instance. – Nikos Baxevanis Mar 18 '13 at 17:57
  • @Nikos - ok, so one concept illustrated here is that the equality assertion isn't bi-directional (meaning, if one indirectly modifies the equality comparator of an object using the proxy, then the object's proxy's equality must be what's exercised - not the equality comparator of the object to which it's being compared (please feel free to edit this comment if I'm using the wrong terminology). Is that right? – Jeff Mar 18 '13 at 18:07
  • 1
    Yes. The equality assertion is symmetric for two *proxied* instances, which means that for proxies `x` and `y` x.Equals(y) returns the same value as y.Equals(x). However, since in the above test the source type isn't proxied only the proxied `Equals` can be exercised. Note that this allows you to perform [very flexible comparisons](http://stackoverflow.com/a/11719316/467754). – Nikos Baxevanis Mar 18 '13 at 18:36
  • @NikosBaxevanis There must be something I'm not at all getting. The `likeness` proxy is generated from `dupe`, and `dupe` has the same property values as `original`. I would expect `likeness` to override Equals to return true if the properties match (actually excluding `Brass`, so only `Strings` would have to match). Since `original.Strings` is equal to `dupe.Strings` I would expect the proxy generated from `dupe` to equal `original`... What is it that I'm missing? – Mark Seemann Mar 18 '13 at 19:34
  • @MarkSeemann, @Nikos - my intent is to use `original` as a sort of [Object Mother](http://martinfowler.com/bliki/ObjectMother.html) for test data. The example is simple, in that I simply create `dupe` right in the test, but in a more complex example, it would be more like `var dupe = sut.Create(original.Brass, original.Strings);`. I'm not sure whether it's clearer to create the `Likeness` using `dupe` or `original` as the source (.AsSource()) - any guidance there? – Jeff Mar 18 '13 at 19:49
  • 1
    @MarkSeemann `CreateProxy` does not copy the property values from the target type to the proxied type. (Once the proxy is created, one has to assign the property values and then do the comparison.) – Nikos Baxevanis Mar 18 '13 at 20:15
  • 1
    That is, I believe, a bug. I've created https://github.com/AutoFixture/AutoFixture/issues/87 to track this. – Mark Seemann Mar 18 '13 at 21:26
  • 1
    Currently, there is work in progress so that the `CreateProxy` method can copy the public fields/properties. That way, there won't be necessary to assign the values on the proxied instance (after calling the CreateProxy method). – Nikos Baxevanis Mar 22 '13 at 07:02