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.
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:
x.Equals(x)
returns true
x.Equals(y) == y.Equals(x)
x.Equals(y) && y.Equals(z) == true
then x.Equals(z) == true
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
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.
There are also other guidelines which are good to use while implementing equality:
==
and !=
operatorAs 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.
System.IEquatable<T>
interfaceThis 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.
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
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] GetHashCodeSuccessiveAssertion class from GetHashCode
implementation is consistentAutoFixture.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; }
}
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.
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)));
}
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.