Often, while writing code we discover value objects in our application. Further, we also need to perform some operation on that types that involve determining equality of our value objects. In .NET there are established rules how to properly implement equality, but testing equality in value objects involves quite a lot of repetitive work. Let’s see what we can do with that and adhere to DRY principle. At first let’s review the rules that we should take into account while implementing equality.

Things you should remember while implementing equality

According to various resources, we should adhere to the properties of equality implementation, which are mentioned for example at MSDN.

So, the implementation of equality should:

  • be reflexive

x.Equals(x) returns true

  • be symmetric

x.Equals(y) == y.Equals(x)

  • be transitive

x.Equals(y) && y.Equals(z) == true then x.Equals(z) == true

  • be consistent

Invocations of x.Equals(y) return the same value as long as the values in x or y are not modified or references are not changed

  • return false when compared against null

x.Equals(null) == false

We should also override GetHashCode method when overriding Equals method, even compiler reminds us (as a warning) about that. This is due to implementation of many .NET classes (for example Hashtable or Dictionary), because they rely on the rule that if two objects are equal, they should return the same hash code value.

Nice, when you do it when implementing equality

There are also other guidelines which are good to use while implementing equality:

  • Overload == and != operator

As default the == and != operators perform identity check. When we provide our equality implementation we should also overload equality and inequality operators to avoid confusion and bugs while working with our objects.

  • Implement System.IEquatable<T> interface

This is an interface which defines a strongly typed Equals method, which can be used internally by other equality comparison methods. It is worth mentioning that using strongly typed Equals method avoids boxing while working with structs.

Test-Do list for testing equality

And last but not least, we must put logic for actually performing the equality comparison of our objects. Finally, this is our test-do list for testing equality implementation:

    [ ] Equals and GetHashCode method are overridden
    [ ] Check if implementation is reflexive
    [ ] Check if implementation is symmetric
    [ ] Check if implementation is transitive
    [ ] Check if implementation is consistent
    [ ] Check if returns false when compared to null
    [ ] Check whether implementation of equality works according to the comparison logic
    [ ] GetHashCode implementation produces correct results
    [ ] GetHashCode implementation is consistent
    [ ] == and != operators are overloaded
    [ ] == and != operators produce correct results
    [ ] IEquatable<T> is implemented
    [ ] IEquatable<T> produces correct results

Write once, run use everywhere

It is a lot of work, and our goal is to avoid repeating this every time we introduce value object. We know what to test for, to check equality implementation correctness. It would be the best if we can simply say what we want to test instead of how the equality tests should be done. That reminds me about the Write once, run everywhere slogan coined by Sun for Java. Actually, in our case we should say Write once, use everywhere. We can apply it here, write reusable component, which performs test cases and we can reuse it when we introduce new value objects to the system.

Fortunately, there is a library that can help us. It is AutoFixture (maybe there are other libraries but I don’t know any), and its part AutoFixture.Idioms. If we use that library, out of the box we can cross four items from test-do list. For the remaining items we can plug into AutoFixture.Idioms infrastructure and write assertions that performs other test cases.

So, this is the test-do list we have after using AutoFixture:

    [ ] Equals and GetHashCode method are overridden
    [x] Check if implementation is reflexive EqualsSelfAssertion class from AutoFixture.Idioms
    [ ] Check if implementation is symmetric
    [ ] Check if implementation is transitive
    [x] Check if implementation is consistent EqualsSuccessiveAssertion class from AutoFixture.Idioms
    [x] Check if returns false when compare to null EqualsNullAssertion class from AutoFixture.Idioms
    [ ] Check whether implementation of equality works according to the comparison logic
    [ ] GetHashCode implementation produces correct results
    [x] GetHashCode implementation is consistent GetHashCodeSuccessiveAssertion class from AutoFixture.Idioms
    [ ] == and != operators are overloaded
    [ ] == and != operators produce correct results
    [ ] IEquatable<T> is implemented
    [ ] IEquatable<T> produces correct results

For clarification, the assertions that will be presented are intended to use with immutable value objects. Data used in equality comparison is provided by arguments passed to constructor. Example of that value object is following:

public class Point
{
    public Point(int x, int y)
    {
        X = x;
        Y = y;
    }

    public int X { get; private set; }
    public int Y { get; private set; }
}

Making EqualityOperatorOverloadAssertion

Goal of the EqualityOperatorOverloadAssertion is to check whether given type overloads == operator. Assertion derives from IdiomaticAssertion abstract class that is defined in AutoFixture.Idioms. IdiomaticAssertion provides helpful default implementation for methods from IIdiomaticAssertion interface. In our example we are overriding Verify method with Type parameter as this is the information necessary to perform interesting us assertion. After it turns out that type does not overload == operator, we throw meaningful exception which explains what is wrong. This is the code for EqualityOperatorOverloadAssertion:

public class EqualityOperatorOverloadAssertion : IdiomaticAssertion
{
	public override void Verify(Type type)
	{
		if (type == null)
		{
			throw new ArgumentNullException("type");
		}

		var equalityOperatorOverload = type.GetEqualityOperatorMethod();

		if (equalityOperatorOverload == null)
		{
			throw new EqualityOperatorException(
				string.Format("Expected type {0} to overload == operator with parameters of type {0}", type.Name));
		}
	}
}

Usage of EqualityOperatorOverloadAssertion:

[Fact]
public void ShouldOverloadEqualityOperator()
{
	new EqualityOperatorOverload().Verify(typeof(SomeValueObject));
}

I think that it doesn’t make sense to put here implementation of every assertion. Therefore, I decided to pick one simple assertion and explain how it is done. If you are interested how other assertions are implemented, please take a look here.

Compose all the assertions

To write a reusable component that will execute test cases from our test-do list we need to compose all created assertions. We can use CompositeIdiomaticAssertion class from AutoFixture.Idioms that allows to supply suite of assertions that will be verified.

public static class EqualityTestsFor<T> where T : class
{
	public static void Assert()
	{
		var fixture = new Fixture();
		var compositeAssertion =
			new CompositeIdiomaticAssertion(EqualityAssertions(fixture, new EqualityTestCaseProvider(fixture)));

		VerifyCompositeAssertion(compositeAssertion);
	}

	public static void Assert(Func<EqualityTestsConfiguration<T>> configuration)
	{
		var compositeAssertion =
			new CompositeIdiomaticAssertion(EqualityAssertions(new Fixture(), configuration()));

		VerifyCompositeAssertion(compositeAssertion);
	}

	private static void VerifyCompositeAssertion(CompositeIdiomaticAssertion compositeAssertion)
	{
		compositeAssertion.Verify(typeof(T));
		compositeAssertion.Verify(typeof(T).GetEqualsMethod());
		compositeAssertion.Verify(typeof(T).GetMethod("GetHashCode"));
	}

	private static IEnumerable<IdiomaticAssertion> EqualityAssertions(ISpecimenBuilder specimenBuilder,
		IEqualityTestCaseProvider equalityTestCaseProvider)
	{
		yield return new EqualsOverrideAssertion();
		yield return new GetHashCodeOverrideAssertion();
		yield return new EqualsSelfAssertion(specimenBuilder);
		yield return new EqualsSymmetricAssertion(specimenBuilder);
		yield return new EqualsTransitiveAssertion(specimenBuilder);
		yield return new EqualsSuccessiveAssertion(specimenBuilder);
		yield return new EqualsNullAssertion(specimenBuilder);
		yield return new EqualsValueCheckAssertion(equalityTestCaseProvider);
		yield return new GetHashCodeValueCheckAssertion(equalityTestCaseProvider);
		yield return new GetHashCodeSuccessiveAssertion(specimenBuilder);
		yield return new EqualityOperatorOverloadAssertion();
		yield return new InequalityOperatorOverloadAssertion();
		yield return new EqualityOperatorValueCheckAssertion(equalityTestCaseProvider);
		yield return new InequalityOperatorValueCheckAssertion(equalityTestCaseProvider);
		yield return new IEquatableImplementedAssertion();
		yield return new IEquatableValueCheckAssertion(equalityTestCaseProvider);
	}
}

If our value object is using only primitive types in constructor, we can rely on default equality test case provider that can handle this situation. It means, that we can write simple unit test for equality like:

[Fact]
public void ShouldImplementValueEquality()
{
	EqualityTestsFor<Point>.Assert();
}

But, if we need some more advanced scenarios, we can provide our own instances to describe what the results of equality test cases should be:

[Fact]
public void ShouldImplementValueEquality()
{
	EqualityTestsFor<Point>.Assert(
		() =>
			EqualityTestsConfigurer<Point>
			.Instance(new Point(1, 1))
				.ShouldBeEqualTo(new Point(1, 1))
				.ShouldNotBeEqualTo(new Point(1, 2))
				.ShouldNotBeEqualTo(new Point(2, 1)));
}

Wrap up

Checking equality implementation in value objects can be repetitive work, hence in this post you can find reusable approach to this problem. AutoFixture was used as a supporting library. It is heavily using role interfaces. Therefore it is very composable and it is a pleasure to work with design like that.

I’d like to add, if we spot in our solution that we are testing something more than once, it is a sign that maybe there is domain concept which should be made explicit. This is a somewhat term Test once, use everywhere. I find it useful, because it avoids duplication of the behavior in the system. We put behavior in one component, test it and we can use it everywhere. It also prevents us for combinatorial explosion of test cases when we have to test the same thing many times.



blog comments powered by Disqus

Published

26 October 2015

Tags