Compare equality between two objects in NUnit

Viewed 96327

I'm trying to assert that one object is "equal" to another object.

The objects are just instances of a class with a bunch of public properties. Is there an easy way to have NUnit assert equality based on the properties?

This is my current solution but I think there may be something better:

Assert.AreEqual(LeftObject.Property1, RightObject.Property1)
Assert.AreEqual(LeftObject.Property2, RightObject.Property2)
Assert.AreEqual(LeftObject.Property3, RightObject.Property3)
...
Assert.AreEqual(LeftObject.PropertyN, RightObject.PropertyN)

What I'm going for would be in the same spirit as the CollectionEquivalentConstraint wherein NUnit verifies that the contents of two collections are identical.

19 Answers

If you can't override Equals for any reason, you can build a helper method that iterates through public properties by reflection and assert each property. Something like this:

public static class AssertEx
{
    public static void PropertyValuesAreEquals(object actual, object expected)
    {
        PropertyInfo[] properties = expected.GetType().GetProperties();
        foreach (PropertyInfo property in properties)
        {
            object expectedValue = property.GetValue(expected, null);
            object actualValue = property.GetValue(actual, null);

            if (actualValue is IList)
                AssertListsAreEquals(property, (IList)actualValue, (IList)expectedValue);
            else if (!Equals(expectedValue, actualValue))
                Assert.Fail("Property {0}.{1} does not match. Expected: {2} but was: {3}", property.DeclaringType.Name, property.Name, expectedValue, actualValue);
        }
    }

    private static void AssertListsAreEquals(PropertyInfo property, IList actualList, IList expectedList)
    {
        if (actualList.Count != expectedList.Count)
            Assert.Fail("Property {0}.{1} does not match. Expected IList containing {2} elements but was IList containing {3} elements", property.PropertyType.Name, property.Name, expectedList.Count, actualList.Count);

        for (int i = 0; i < actualList.Count; i++)
            if (!Equals(actualList[i], expectedList[i]))
                Assert.Fail("Property {0}.{1} does not match. Expected IList with element {1} equals to {2} but was IList with element {1} equals to {3}", property.PropertyType.Name, property.Name, expectedList[i], actualList[i]);
    }
}

Override .Equals for your object and in the unit test you can then simply do this:

Assert.AreEqual(LeftObject, RightObject);

Of course, this might mean you just move all the individual comparisons to the .Equals method, but it would allow you to reuse that implementation for multiple tests, and probably makes sense to have if objects should be able to compare themselves with siblings anyway.

I prefer not to override Equals just to enable testing. Don't forget that if you do override Equals you really should override GetHashCode also or you may get unexpected results if you are using your objects in a dictionary for example.

I do like the reflection approach above as it caters for the addition of properties in the future.

For a quick and simple solution however its often easiest to either create a helper method that tests if the objects are equal, or implement IEqualityComparer on a class you keep private to your tests. When using IEqualityComparer solution you dont need to bother with the implementation of GetHashCode. For example:

// Sample class.  This would be in your main assembly.
class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

// Unit tests
[TestFixture]
public class PersonTests
{
    private class PersonComparer : IEqualityComparer<Person>
    {
        public bool Equals(Person x, Person y)
        {
            if (x == null && y == null)
            {
                return true;
            }

            if (x == null || y == null)
            {
                return false;
            }

            return (x.Name == y.Name) && (x.Age == y.Age);
        }

        public int GetHashCode(Person obj)
        {
            throw new NotImplementedException();
        }
    }

    [Test]
    public void Test_PersonComparer()
    {
        Person p1 = new Person { Name = "Tom", Age = 20 }; // Control data

        Person p2 = new Person { Name = "Tom", Age = 20 }; // Same as control
        Person p3 = new Person { Name = "Tom", Age = 30 }; // Different age
        Person p4 = new Person { Name = "Bob", Age = 20 }; // Different name.

        Assert.IsTrue(new PersonComparer().Equals(p1, p2), "People have same values");
        Assert.IsFalse(new PersonComparer().Equals(p1, p3), "People have different ages.");
        Assert.IsFalse(new PersonComparer().Equals(p1, p4), "People have different names.");
    }
}

I agree with ChrisYoxall -- implementing Equals in your main code purely for testing purposes is not good.

If you are implementing Equals because some application logic requires it, then that's fine, but keep pure testing-only code out of cluttering up stuff (also the semantics of checking the same for testing may be different than what your app requires).

In short, keep testing-only code out of your class.

Simple shallow comparison of properties using reflection should be enough for most classes, although you may need to recurse if your objects have complex properties. If following references, beware of circular references or similar.

Sly

I've ended with writing a simple expression factory:

public static class AllFieldsEqualityComprision<T>
{
    public static Comparison<T> Instance { get; } = GetInstance();

    private static Comparison<T> GetInstance()
    {
        var type = typeof(T);
        ParameterExpression[] parameters =
        {
            Expression.Parameter(type, "x"),
            Expression.Parameter(type, "y")
        };
        var result = type.GetProperties().Aggregate<PropertyInfo, Expression>(
            Expression.Constant(true),
            (acc, prop) =>
                Expression.And(acc,
                    Expression.Equal(
                        Expression.Property(parameters[0], prop.Name),
                        Expression.Property(parameters[1], prop.Name))));
        var areEqualExpression = Expression.Condition(result, Expression.Constant(0), Expression.Constant(1));
        return Expression.Lambda<Comparison<T>>(areEqualExpression, parameters).Compile();
    }
}

and just use it:

Assert.That(
    expectedCollection, 
    Is.EqualTo(actualCollection)
      .Using(AllFieldsEqualityComprision<BusinessCategoryResponse>.Instance));

It's very useful since I have to compare collection of such objects. And you can use this comparere somewhere else :)

Here is gist with example: https://gist.github.com/Pzixel/b63fea074864892f9aba8ffde312094f

I know this is a really old question, but NUnit still doesn't have native support for this. However, if you like BDD-style testing (ala Jasmine), you'd be pleasantly surprised with NExpect (https://github.com/fluffynuts/NExpect, get it from NuGet), which has deep equality testing baked right in there.

(disclaimer: I am the author of NExpect)

Here is just a modified version of one of the answers above that can be used with Moq:

public static class Helpers {

    public static bool DeepCompare(this object actual, object expected) {
        var properties = expected.GetType().GetProperties();
        foreach (var property in properties) {
            var expectedValue = property.GetValue(expected, null);
            var actualValue = property.GetValue(actual, null);

            if (actualValue == null && expectedValue == null) {
                return true;
            }

            if (actualValue == null || expectedValue == null) {
                return false;
            }

            if (actualValue is IList actualList) {
                if (!AreListsEqual(actualList, (IList)expectedValue)) {
                    return false;
                }
            }
            else if (IsValueType(expectedValue)) {
                if(!Equals(expectedValue, actualValue)) {
                    return false;
                }
            }
            else if (expectedValue is string) {
                return actualValue is string && Equals(expectedValue, actualValue);
            }
            else if (!DeepCompare(expectedValue, actualValue)) {
                return false;
            }
                
        }
        return true;
    }

    private static bool AreListsEqual(IList actualList, IList expectedList) {
        if (actualList == null && expectedList == null) {
            return true;
        }

        if (actualList == null  || expectedList == null) {
            return false;
        }

        if (actualList.Count != expectedList.Count) {
            return false;
        }

        if (actualList.Count == 0) {
            return true;
        }

        var isValueTypeOrString = IsValueType(actualList[0]) || actualList[0] is string;

        if (isValueTypeOrString) {
            for (var i = 0; i < actualList.Count; i++) {
                if (!Equals(actualList[i], expectedList[i])) {
                    return false;
                }
            }
        }
        else {
            for (var i = 0; i < actualList.Count; i++) {
                if (!DeepCompare(actualList[i], expectedList[i])) {
                    return false;
                }
            }
        }

        return true;
    }

    private static bool IsValueType(object obj) {
        return obj != null && obj.GetType().IsValueType;
    }

It can be used to match an object when specifying a setup on a mocked type when you need something more than It.IsAny<> and want to match on all properties, like this:

_clientsMock.Setup(m => m.SearchClients(
            It.Is<SearchClientsPayload>(x => x.DeepCompare(expectedRequest)))).Returns(expectedResponse);

It can of course be improved to work with enumerables and other complex scenarios.

Related