Introducing AutoFixture Likeness by Mark Seemann
The last time I presented a sample of an AutoFixture-based unit test, I purposely glossed over the state-based verification that asserted that the resulting state of the basket variable was that the appropriate Pizza was added:
Assert.IsTrue(basket.Pizze.Any(p => p.Name == pizza.Name), "Basket has added pizza.");
The main issue with this assertion is that the implied equality expression is rather weak: we consider a PizzaPresenter instance to be equal to a Pizza instance if their Name properties match.
What if they have other properties (like Size) that don't match? If this is the case, the test would be a false negative. A match would be found in the Pizze collection, but the instances would not truly represent the same pizza.
How do we resolve this conundrum without introducing equality pollution? AutoFixture offers one option in the form of the generic Likeness<TSource, TDestination> class. This class offers convention-based test-specific equality mapping from TSource to TDestination and overriding the Equals method.
One of the ways we can use it is by a convenience extension method. This unit test is a refactoring of the test from the previous post, but now using Likeness:
[TestMethod] public void AddWillAddToBasket_Likeness() { // Fixture setup var fixture = new Fixture(); fixture.Register<IPizzaMap, PizzaMap>(); var basket = fixture.Freeze<Basket>(); var pizza = fixture.CreateAnonymous<PizzaPresenter>(); var expectedPizza = pizza.AsSource().OfLikeness<Pizza>(); var sut = fixture.CreateAnonymous<BasketPresenter>(); // Exercise system sut.Add(pizza); // Verify outcome Assert.IsTrue(basket.Pizze.Any(expectedPizza.Equals)); // Teardown }
Notice how the Likeness instance is created with the AsSource() extension method. The pizza instance (of type PizzaPresenter) is the source of the Likeness, whereas the Pizza domain model type is the destination. The expectedPizza instance is of type Likeness<PizzaPresenter, Pizza>.
The Likeness class overrides Equals with a convention-based comparison: if two properties have the same name and type, they are equal if their values are equal. All public properties on the destination must have equal properties on the source.
This allows me to specify the Equals method as the predicate for the Any method in the assertion:
Assert.IsTrue(basket.Pizze.Any(expectedPizza.Equals));
When the Any method evalues the Pizze collection, it executes the Equals method on Likeness, resulting in a convention-based comparison of all public properties and fields on the two instances.
It's possible to customize the comparison to override the behavior for certain properties, but I will leave that to later posts. This post only scratches the surface of what Likeness can do.
To use Likeness, you must add a reference to the Ploeh.SemanticComparison assembly. You can create a new instance using the public constructor, but to use the AsSource extension method, you will need to add a using directive:
using Ploeh.SemanticComparison.Fluent;
Comments
In your example, you are only comparing one property and I know that you can test many properties as well.
Now it is my understanding that given many properties if any property doesn't match, then you'll get a test failure. My question is how to output a message pinpointing which property is causing the test to fail.
On another note, maybe you could ask Adam Ralph how he's integrated the comment section on his blog, which I believe is using the same platform as you are. http://adamralph.com/2013/01/09/blog-post-excerpts-a-new-solution/
David, if you want to get more detailed feedback on which properties don't match, you can use
expected.ShouldEqual(actual);