Asserting Equality in your C# unit tests

Paulo Gomes
5 min readOct 18, 2017

--

When you start creating your first unit tests you are bound to encounter some situations in which you are trying to compare two values, an actual and an expected. All their properties have the exactly same content, however the Assert.Equal (or Assert.AreEqual if you are using NUnit) will simply not state that they are equal. An example of that would be the code below:

Customer actual = new Customer { Id = 1, Name = "John" };
Customer expected = new Customer { Id = 1, Name = "John" };

Assert.Equal(expected, actual); // The test will fail here

However, if you change the assert to be based on each property, they will be equal and your test will pass:

Assert.Equal(expected.Id, actual.Id);
Assert.Equal(expected.Name, actual.Name);

The first example fails due to the way comparison works for reference types. By default, the equality operation for those types will only assert whether the two objects being compared are the same, namely your variables are pointing to the same object within the memory heap. Due to this, out of the box, both .Equal and .Same assertions will always result in the same outcome and will only be positive in cases such as this:

Customer john = new Customer { Id = 1, Name = "John" };
Customer john2 = john;
Assert.Equal(john, john2);
Assert.Same(john, john2);

On the other hand, the second example in which we were asserting the properties inside Customer, worked. The property Id is of type System.Int32, a value type, so its comparison will actually check whether the objects have the same value. The property Name is of type System.String, which although is a reference type, it behaves like value types for comparison purposes.

TL;DR

If the only thing you want to do is to get your tests to pass when comparing two custom types, I recommend using the nuget package FluentAssertions. It has an extension method which will add “.ShouldBeEquivalentTo()” to all your objects, allowing you to test for object equivalence, instead of equality.

Behind the scenes, this method will go through all the properties of the object being compared and will assert one by one, removing the need for you to do so.

Customer customer1 = new Customer { Id = 1, Name = "John" };
Customer customer2 = new Customer { Id = 1, Name = "John" };

customer1.ShouldBeEquivalentTo(customer2);

This approach will also support arrays, lists, etc:

var customers1 = new [] { new Customer { Id = 1, Name = "John" }, new Customer { Id = 2, Name = "Jack" } };
var customers2 = new [] { new Customer { Id = 1, Name = "John" }, new Customer { Id = 2, Name = "Jack" } };

customers1.ShouldBeEquivalentTo(customers2);

Allowing you to have clearer and more concise test assertions, also helping you to comply with the one assertion per unit test best practice.

More On Equality

Now if you are indeed interested on the subject, and want to know a bit more about object equality, keep on reading. :)

Oversimplifying it, every time you check two objects for equality, as in a == b, a call will be made to the Equals method to compare the two objects. If the type in question don’t have an Equals method implemented, .Net will use the default implementation which will either be inherited from System.Object, when considering reference types, or System.ValueType, for value types.

The official documentation is quite concise explaining the difference in behaviour between the two:

If the current instance is a reference type, the Equals(Object) method tests for reference equality, and a call to the Equals(Object)method is equivalent to a call to the ReferenceEquals method. Reference equality means that the object variables that are compared refer to the same object.

If the current instance is a value type, the Equals(Object) method tests for value equality. Value equality means the following:

The two objects are of the same type... a Byte object that has a value of 12 does not equal an Int32 object that has a value of 12, because the two objects have different run-time types.

That is the out-of-the-box behaviour, but it can be modified for user-defined types, and you should actually modify it when it comes down to value types — after all, by they very nature they represent values and the default approach won’t hold water.

But doing this properly can get complicated. As a rule of thumb, every time you override Equals you also need to override GetHashCode. Their usage is intertwined on pretty fundamental types within the BCL, so they need to be aligned at all times. Messing up you here will lead to unexpected behaviour when playing with things like Dictionary or Hashtables.

To do this right you need to do 3 things, here’s the role of each one on checking for equality:

1. Implement the IEquatable<T> Interface

The IEquatable<T> interface is used by generic collection objects such as Dictionary<TKey, TValue>, List<T>, and LinkedList<T>when testing for equality in such methods as Contains, IndexOf, LastIndexOf, and Remove. It should be implemented for any object that might be stored in a generic collection.

2. Override Object.Equals(Object)

Determines whether the specified object is equal to the current object.

3. Override Object.GetHashCode

A hash code is a numeric value that is used to insert and identify an object in a hash-based collection such as the Dictionary<TKey, TValue> class, the Hashtable class, or a type derived from the DictionaryBase class. The GetHashCode method provides this hash code for algorithms that need quick checks of object equality.

Here’s an implementation created using ReSharper that helps on those 3 points:

public class Point : IEquatable<Point>
{
protected readonly int x, y;

public Point(int xValue, int yValue)
{
x = xValue;
y = yValue;
}

public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != this.GetType()) return false;
return Equals((Point) obj);
}

public override int GetHashCode()
{
unchecked
{
return (x * 397) ^ y;
}
}

public bool Equals(Point other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return x == other.x && y == other.y;
}
}

Do not take this lightly and certainly do not implement this just to make your tests pass. As a rule of thumb, your tests should drive your design and the development of your production code. However, you should not change application behaviour as a side effect of making your tests pass.

If you decide to go ahead with changing the equality behaviour of your user-defined types, please follow Microsoft’s guidelines on this:

  • Implement the GetHashCode method whenever you implement the Equals method. This keeps Equals and GetHashCode synchronized.
  • Override the Equals method whenever you implement the equality operator (==), and make them do the same thing. This allows infrastructure code such as Hashtable and ArrayList, which use the Equals method, to behave the same way as user code written using the equality operator.
  • Do not throw exceptions from the Equals or GetHashCode methods or the equality operator (==).
  • For reference types, you generally should not need to override the equality operator (==).
  • For value types, you generally should override Equals, GetHashCode and the equality operator (==).

--

--

Paulo Gomes

Software craftsman on the eternal learning path towards (hopefully) mastery. Security enthusiast keen on SecDevOps. My opinions are my own.