156

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.

Julian
  • 33,915
  • 22
  • 119
  • 174
Michael Haren
  • 105,752
  • 40
  • 168
  • 205
  • See: https://github.com/GregFinzer/Compare-Net-Objects It contains test extensions to do what you are looking for: https://github.com/GregFinzer/Compare-Net-Objects/wiki/Test-Extensions – phoenix Mar 02 '22 at 20:22

20 Answers20

144

Do not override Equals just for testing purposes. It's tedious and affects domain logic. Instead,

Use JSON to compare the object's data

No additional logic on your objects. No extra tasks for testing.

Just use this simple method:

public static void AreEqualByJson(object expected, object actual)
{
    var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
    var expectedJson = serializer.Serialize(expected);
    var actualJson = serializer.Serialize(actual);
    Assert.AreEqual(expectedJson, actualJson);
}

It seems to work out great. The test runner results info will show the JSON string comparison (the object graph) included so you see directly what's wrong.

Also note! If you have bigger complex objects and just want to compare parts of them you can (use LINQ for sequence data) create anonymous objects to use with above method.

public void SomeTest()
{
    var expect = new { PropA = 12, PropB = 14 };
    var sut = loc.Resolve<SomeSvc>();
    var bigObjectResult = sut.Execute(); // This will return a big object with loads of properties 
    AssExt.AreEqualByJson(expect, new { bigObjectResult.PropA, bigObjectResult.PropB });
}
Community
  • 1
  • 1
Max
  • 3,280
  • 2
  • 26
  • 30
  • 1
    This is an excellent way to test, especially if you're anyways dealing with JSON (e.g. using a typed client to access a web service). This answer should be much higher. – Roopesh Shenoy Mar 22 '13 at 22:35
  • Your solution worked great for me thanks Max, I tried to add my own version in the comments but there wasn't enough space so have submitted it as a separate answer. – samaspin May 29 '13 at 16:42
  • It is really good and simple decision. I really like it. But it is not works when there is ICollection property in object. – Dmitresky May 20 '14 at 18:12
  • 1
    Use Linq! @DmitryBLR (see last paragraph in answer) :) – Max Jun 03 '14 at 10:40
  • 3
    This is a great idea. I would use the newer Json.NET: var expectedJson = Newtonsoft.Json.JsonConvert.SerializeObject(expected); – BrokeMyLegBiking Nov 14 '14 at 11:42
  • I've added sample code to assert parts of object. Makes sense @CoffeeAddict ? – Max Dec 02 '14 at 22:27
  • 2
    This won't work with circular references. Use https://github.com/kbilsted/StatePrinter/ instead for an improved experience over the JSON approach – Carlo V. Dango Mar 07 '15 at 18:51
  • This wont work if objects are same, but the order in lists is not, so it's not universal – Konstantin Chernov Aug 21 '15 at 07:22
  • 2
    That's true @KokaChernov and sometimes you want to fail test if not the ordering is same but if you don't want to fail if ordering is not same you can make explicit sort (using linq) on the lists before passing them to AreEqualByJson method. A simple variant of "re-arranging" your objects before testing is in last code example in the answer. So that's very "universal" I think! :) – Max Aug 21 '15 at 08:27
  • 1
    @MaxWikström indeed, but I what about the situation when you have no expicit order and no way to introduce it. Currently my noSQL storage repo returns me mixed order in sublists, so, probably I can use linq and invent some way to order it, but it's not so trivial. So, you see, I need some cool out-of-the-box solution not to mess around with custom code-patching. Anyway, upvoted. – Konstantin Chernov Aug 21 '15 at 10:34
  • won't allow you to compare doubles or decimals with a given precision. – Noctis Oct 15 '19 at 05:55
  • Make sure that your object is serializable and all properties you want to compare are serializable also. – user2291296 Jul 07 '21 at 19:53
122

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]);
    }
}
Noctis
  • 11,507
  • 3
  • 43
  • 82
Juanma
  • 3,961
  • 3
  • 27
  • 26
  • @wesley: this is not true. Type.GetProperties Method: Returns all the public properties of the current Type. See http://msdn.microsoft.com/en-us/library/aky14axb.aspx – Sergii Volchkov Jun 07 '11 at 10:48
  • 4
    thanks. however, I had to switch the order of the actual and expected params since converntion is that expected is a param before actual. – Valamas Sep 07 '11 at 05:49
  • 1
    this is a better approach IMHO, Equal & HashCode overrides shouldn't have to be based on comparing every field and plus that's very tedious to do across every object. Good job! – Scott White Jul 13 '12 at 15:29
  • 3
    This works great if your type only has basic types as properties. However if your type has properties with custom types (that don't implement Equals) it will fail. – Bobby Cannon Aug 30 '13 at 20:24
  • Added some recursion for object properties, but I had to skip over indexed properties: – cerhart Nov 19 '13 at 12:18
  • The code in this answer is a good starting point, but doesn't work for IEnumerables that aren't ILists, or objects that contain properties that don't implement Equals. I expanded it to work for those cases: https://gist.github.com/rotoclone/7cf50cbbb974853070368942a9f475a7 – the_nacho Apr 25 '20 at 17:00
114

Try FluentAssertions library:

dto.Should().BeEquivalentTo(customer) 

It can also be installed using NuGet.

Matthew
  • 1,630
  • 1
  • 14
  • 19
dkl
  • 3,850
  • 2
  • 27
  • 26
  • 20
    ShouldHave has been deprecated, so should be dto.ShouldBeEquivalentTo( customer ); instead – WhiteKnight Jan 30 '14 at 10:15
  • 3
    This is is the best answer for [this reason](http://stackoverflow.com/a/28545338/62600). – Todd Menier Feb 16 '15 at 15:56
  • 2
    ShouldBeEquivalent is buggy :( – Konstantin Chernov Aug 21 '15 at 07:20
  • @KokaChernov In what way is ShouldBeEquivalent buggy ? – riezebosch Aug 21 '15 at 13:59
  • @riezebosch Just stumbled upon weird exception while comparing 2 objects and had to abandon it's usage in favor of manual `.Equals` `.GetHashset` implementation. probably would post a bug request later this day – Konstantin Chernov Aug 21 '15 at 15:18
  • 3
    just had the same problem and used the following which seems to be working fine: `actual.ShouldBeEquivalentTo(expected, x => x.ExcludingMissingMembers())` – dragonfly02 Sep 30 '15 at 17:05
  • This is old but a word of note that currently ShouldBeEquivalentTo won't work if you're doing anything somewhat advanced in your mapping like flattening an object. So if you have `source.City.Name` and `destination.CityName` then `destination.ShouldBeEquivalentTo(source, x => x.ExcludingMissingMembers());` will not check that `destination.CityName == source.City.Name`. – Michael Feb 20 '19 at 19:12
  • 1
    This is a great lib! Does not require to override Equals and also (if equals is overriden anyway, e.g. for value objects) does not rely on correct implementation. Also the difference is printed nicely, like Hamcrest does for Java. – kap May 03 '19 at 20:54
  • 3
    In version 5.10.3 the syntax is dto.Should().BeEquivalentTo(customer) – NJS Jul 19 '20 at 20:11
53

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.

Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825
  • 2
    Thanks, lassevk. This worked for me! I implemented .Equals according to the guidelines here: http://msdn.microsoft.com/en-us/library/336aedhh(VS.80).aspx – Michael Haren Nov 25 '08 at 17:50
  • 14
    And GetHashCode(), obviously ;-p – Marc Gravell Aug 23 '09 at 13:28
  • 1
    One important caveat: if your object also implements `IEnumerable` it'll be compared as a collection regardless of overriding implementations of `Equals` because NUnit gives `IEnumerable` higher precedence. See the `NUnitEqualityComparer.AreEqual` methods for details. You can override the comparer by using one of the equality constraint's `Using()` methods. Even then, it's not sufficient to implement the non-generic `IEqualityComparer` because of the adapter NUnit uses. – Kaleb Pederson Oct 02 '13 at 19:42
  • 16
    More Caveat: Implementing `GetHashCode()` on mutable types will misbehave if you ever use that object as a key. IMHO, overriding `Equals()`, `GetHashCode()` and making the object immutable just for testing does not make sense. – bavaza Dec 09 '13 at 06:51
39

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.");
    }
}
Bobby Cannon
  • 6,665
  • 8
  • 33
  • 46
Chris Yoxall
  • 398
  • 2
  • 5
  • The equals does not handle null values. I'd add the following before your return statement in the equals method. if (x == null && y == null) { return true; } if (x == null || y == null) { return false; } I edited the question to add null support. – Bobby Cannon Aug 30 '13 at 20:34
  • Not working for me with throw new NotImplementedException(); in the GetHashCode. Why do I need that function in the IEqualityComparer either way ? – love2code Mar 07 '20 at 01:58
26

I've tried several approaches mentioned here. Most involve serializing your objects and doing a string compare. While super easy and generally very effective, I've found it comes up a little short when you have a failure and something like this gets reported:

Expected string length 2326 but was 2342. Strings differ at index 1729.

Figuring out where where the differences are is a pain to say the least.

With FluentAssertions' object graph comparisons (i.e. a.ShouldBeEquivalentTo(b)), you get this back:

Expected property Name to be "Foo" but found "Bar"

That's much nicer. Get FluentAssertions now, you'll be glad later (and if you upvote this, please also upvote dkl's answer where FluentAssertions was first suggested).

Community
  • 1
  • 1
Todd Menier
  • 37,557
  • 17
  • 150
  • 173
10

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

Sly Gryphon
  • 3,751
  • 1
  • 25
  • 17
  • Nice catch on circular references. Easy to overcome if you keep a dictionary of objects already in the comparison tree. – Lucas B Nov 13 '09 at 16:09
8

Property constraints, added in NUnit 2.4.2, allow a solution that is more readable than the OP's original one, and it produces much better failure messages. It's not in any way generic, but if you don't need to do it for too many classes, it's a very adequate solution.

Assert.That(ActualObject, Has.Property("Prop1").EqualTo(ExpectedObject.Prop1)
                          & Has.Property("Prop2").EqualTo(ExpectedObject.Prop2)
                          & Has.Property("Prop3").EqualTo(ExpectedObject.Prop3)
                          // ...

Not as general-purpose as implementing Equals but it does give a much better failure message than

Assert.AreEqual(ExpectedObject, ActualObject);
Paul Hicks
  • 13,289
  • 5
  • 51
  • 78
4

Max Wikstrom's JSON solution (above) makes the most sense to me, it's short, clean and most importantly it works. Personally though I'd prefer to implement the JSON conversion as a separate method and place the assert back inside the unit test like this...

HELPER METHOD:

public string GetObjectAsJson(object obj)
    {
        System.Web.Script.Serialization.JavaScriptSerializer oSerializer = new System.Web.Script.Serialization.JavaScriptSerializer();
        return oSerializer.Serialize(obj);
    }

UNIT TEST :

public void GetDimensionsFromImageTest()
        {
            Image Image = new Bitmap(10, 10);
            ImageHelpers_Accessor.ImageDimensions expected = new ImageHelpers_Accessor.ImageDimensions(10,10);

            ImageHelpers_Accessor.ImageDimensions actual;
            actual = ImageHelpers_Accessor.GetDimensionsFromImage(Image);

            /*USING IT HERE >>>*/
            Assert.AreEqual(GetObjectAsJson(expected), GetObjectAsJson(actual));
        }

FYI - You may need to add a reference to System.Web.Extensions in your solution.

samaspin
  • 2,342
  • 1
  • 26
  • 31
4

Just install ExpectedObjects from Nuget, you can easily compare two objects's property value, each object value of collection, two composed object's value and partial compare property value by anonymous type.

I have some examples on github: https://github.com/hatelove/CompareObjectEquals

Here were some examples that contain scenarios of comparing object:

    [TestMethod]
    public void Test_Person_Equals_with_ExpectedObjects()
    {
        //use extension method ToExpectedObject() from using ExpectedObjects namespace to project Person to ExpectedObject
        var expected = new Person
        {
            Id = 1,
            Name = "A",
            Age = 10,
        }.ToExpectedObject();

        var actual = new Person
        {
            Id = 1,
            Name = "A",
            Age = 10,
        };

        //use ShouldEqual to compare expected and actual instance, if they are not equal, it will throw a System.Exception and its message includes what properties were not match our expectation.
        expected.ShouldEqual(actual);
    }

    [TestMethod]
    public void Test_PersonCollection_Equals_with_ExpectedObjects()
    {
        //collection just invoke extension method: ToExpectedObject() to project Collection<Person> to ExpectedObject too
        var expected = new List<Person>
        {
            new Person { Id=1, Name="A",Age=10},
            new Person { Id=2, Name="B",Age=20},
            new Person { Id=3, Name="C",Age=30},
        }.ToExpectedObject();

        var actual = new List<Person>
        {
            new Person { Id=1, Name="A",Age=10},
            new Person { Id=2, Name="B",Age=20},
            new Person { Id=3, Name="C",Age=30},
        };

        expected.ShouldEqual(actual);
    }

    [TestMethod]
    public void Test_ComposedPerson_Equals_with_ExpectedObjects()
    {
        //ExpectedObject will compare each value of property recursively, so composed type also simply compare equals.
        var expected = new Person
        {
            Id = 1,
            Name = "A",
            Age = 10,
            Order = new Order { Id = 91, Price = 910 },
        }.ToExpectedObject();

        var actual = new Person
        {
            Id = 1,
            Name = "A",
            Age = 10,
            Order = new Order { Id = 91, Price = 910 },
        };

        expected.ShouldEqual(actual);
    }

    [TestMethod]
    public void Test_PartialCompare_Person_Equals_with_ExpectedObjects()
    {
        //when partial comparing, you need to use anonymous type too. Because only anonymous type can dynamic define only a few properties should be assign.
        var expected = new
        {
            Id = 1,
            Age = 10,
            Order = new { Id = 91 }, // composed type should be used anonymous type too, only compare properties. If you trace ExpectedObjects's source code, you will find it invoke config.IgnoreType() first.
        }.ToExpectedObject();

        var actual = new Person
        {
            Id = 1,
            Name = "B",
            Age = 10,
            Order = new Order { Id = 91, Price = 910 },
        };

        // partial comparing use ShouldMatch(), rather than ShouldEqual()
        expected.ShouldMatch(actual);
    }

Reference:

  1. ExpectedObjects github
  2. Introduction of ExpectedObjects
In91
  • 141
  • 1
  • 2
3

This is a pretty old thread but I was wondering if there's a reason why no answer proposed NUnit.Framework.Is.EqualTo and NUnit.Framework.Is.NotEqualTo?

Such as:

Assert.That(LeftObject, Is.EqualTo(RightObject)); 

and

Assert.That(LeftObject, Is.Not.EqualTo(RightObject)); 
user2315856
  • 162
  • 2
  • 9
2

Another option is to write a custom constraint by implementing the NUnit abstract Constraint class. With a helper class to provide a little syntactic sugar, the resulting test code is pleasantly terse and readable e.g.

Assert.That( LeftObject, PortfolioState.Matches( RightObject ) ); 

For an extreme example, consider class which has 'read-only' members, is not IEquatable, and you could not change the class under test even if you wanted to:

public class Portfolio // Somewhat daft class for pedagogic purposes...
{
    // Cannot be instanitated externally, instead has two 'factory' methods
    private Portfolio(){ }

    // Immutable properties
    public string Property1 { get; private set; }
    public string Property2 { get; private set; }  // Cannot be accessed externally
    public string Property3 { get; private set; }  // Cannot be accessed externally

    // 'Factory' method 1
    public static Portfolio GetPortfolio(string p1, string p2, string p3)
    {
        return new Portfolio() 
        { 
            Property1 = p1, 
            Property2 = p2, 
            Property3 = p3 
        };
    }

    // 'Factory' method 2
    public static Portfolio GetDefault()
    {
        return new Portfolio() 
        { 
            Property1 = "{{NONE}}", 
            Property2 = "{{NONE}}", 
            Property3 = "{{NONE}}" 
        };
    }
}

The contract for the Constraint class requires one to override Matches and WriteDescriptionTo (in the case of a mismatch, a narrative for the expected value) but also overriding WriteActualValueTo (narrative for actual value) makes sense:

public class PortfolioEqualityConstraint : Constraint
{
    Portfolio expected;
    string expectedMessage = "";
    string actualMessage = "";

    public PortfolioEqualityConstraint(Portfolio expected)
    {
        this.expected = expected;
    }

    public override bool Matches(object actual)
    {
        if ( actual == null && expected == null ) return true;
        if ( !(actual is Portfolio) )
        { 
            expectedMessage = "<Portfolio>";
            actualMessage = "null";
            return false;
        }
        return Matches((Portfolio)actual);
    }

    private bool Matches(Portfolio actual)
    {
        if ( expected == null && actual != null )
        {
            expectedMessage = "null";
            expectedMessage = "non-null";
            return false;
        }
        if ( ReferenceEquals(expected, actual) ) return true;

        if ( !( expected.Property1.Equals(actual.Property1)
                 && expected.Property2.Equals(actual.Property2) 
                 && expected.Property3.Equals(actual.Property3) ) )
        {
            expectedMessage = expected.ToStringForTest();
            actualMessage = actual.ToStringForTest();
            return false;
        }
        return true;
    }

    public override void WriteDescriptionTo(MessageWriter writer)
    {
        writer.WriteExpectedValue(expectedMessage);
    }
    public override void WriteActualValueTo(MessageWriter writer)
    {
        writer.WriteExpectedValue(actualMessage);
    }
}

Plus the helper class:

public static class PortfolioState
{
    public static PortfolioEqualityConstraint Matches(Portfolio expected)
    {
        return new PortfolioEqualityConstraint(expected);
    }

    public static string ToStringForTest(this Portfolio source)
    {
        return String.Format("Property1 = {0}, Property2 = {1}, Property3 = {2}.", 
            source.Property1, source.Property2, source.Property3 );
    }
}

Example usage:

[TestFixture]
class PortfolioTests
{
    [Test]
    public void TestPortfolioEquality()
    {
        Portfolio LeftObject 
            = Portfolio.GetDefault();
        Portfolio RightObject 
            = Portfolio.GetPortfolio("{{GNOME}}", "{{NONE}}", "{{NONE}}");

        Assert.That( LeftObject, PortfolioState.Matches( RightObject ) );
    }
}
onedaywhen
  • 55,269
  • 12
  • 100
  • 138
1

I would build on the answer of @Juanma. However, I believe this should not be implemented with unit test assertions. This is a utility that could very well be used in some circumstances by non-test code.

I wrote an article on the matter http://timoch.com/blog/2013/06/unit-test-equality-is-not-domain-equality/

My proposal is as follow:

/// <summary>
/// Returns the names of the properties that are not equal on a and b.
/// </summary>
/// <param name="a"></param>
/// <param name="b"></param>
/// <returns>An array of names of properties with distinct 
///          values or null if a and b are null or not of the same type
/// </returns>
public static string[] GetDistinctProperties(object a, object b) {
    if (object.ReferenceEquals(a, b))
        return null;
    if (a == null)
        return null;
    if (b == null)
        return null;

    var aType = a.GetType();
    var bType = b.GetType();

    if (aType != bType)
        return null;

    var props = aType.GetProperties();

    if (props.Any(prop => prop.GetIndexParameters().Length != 0))
        throw new ArgumentException("Types with index properties not supported");

    return props
        .Where(prop => !Equals(prop.GetValue(a, null), prop.GetValue(b, null)))
        .Select(prop => prop.Name).ToArray();
} 

Using this with NUnit

Expect(ReflectionUtils.GetDistinctProperties(tile, got), Empty);

yields the following message on mismatch.

Expected: <empty>
But was:  < "MagmaLevel" >
at NUnit.Framework.Assert.That(Object actual, IResolveConstraint expression, String message, Object[] args)
at Undermine.Engine.Tests.TileMaps.BasicTileMapTests.BasicOperations() in BasicTileMapTests.cs: line 29
TiMoch
  • 1,008
  • 1
  • 10
  • 17
1

https://github.com/kbilsted/StatePrinter has been written specifically to dump object graphs to string representation with the aim of writing easy unit tests.

  • It comes witg Assert methods that output a properly escaped string easy copy-paste into the test to correct it.
  • It allows unittest to be automatically re-written
  • It integrates with all unit testing frameworks
  • Unlike JSON serialization, circular references are supported
  • You can easily filter, so only parts of types are dumped

Given

class A
{
  public DateTime X;
  public DateTime Y { get; set; }
  public string Name;
}

You can in a type safe manner, and using auto-completion of visual studio include or exclude fields.

  var printer = new Stateprinter();
  printer.Configuration.Projectionharvester().Exclude<A>(x => x.X, x => x.Y);

  var sut = new A { X = DateTime.Now, Name = "Charly" };

  var expected = @"new A(){ Name = ""Charly""}";
  printer.Assert.PrintIsSame(expected, sut);
Carlo V. Dango
  • 13,322
  • 16
  • 71
  • 114
1

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

Alex Zhukovskiy
  • 9,565
  • 11
  • 75
  • 151
0

Deserialize both classes, and do a string compare.

EDIT: Works perfectly, this is the output I get from NUnit;

Test 'Telecom.SDP.SBO.App.Customer.Translator.UnitTests.TranslateEaiCustomerToDomain_Tests.TranslateNew_GivenEaiCustomer_ShouldTranslateToDomainCustomer_Test("ApprovedRatingInDb")' failed:
  Expected string length 2841 but was 5034. Strings differ at index 443.
  Expected: "...taClasses" />\r\n  <ContactMedia />\r\n  <Party i:nil="true" /..."
  But was:  "...taClasses" />\r\n  <ContactMedia>\r\n    <ContactMedium z:Id="..."
  ----------------------------------------------^
 TranslateEaiCustomerToDomain_Tests.cs(201,0): at Telecom.SDP.SBO.App.Customer.Translator.UnitTests.TranslateEaiCustomerToDomain_Tests.Assert_CustomersAreEqual(Customer expectedCustomer, Customer actualCustomer)
 TranslateEaiCustomerToDomain_Tests.cs(114,0): at Telecom.SDP.SBO.App.Customer.Translator.UnitTests.TranslateEaiCustomerToDomain_Tests.TranslateNew_GivenEaiCustomer_ShouldTranslateToDomainCustomer_Test(String custRatingScenario)

EDIT TWO: The two objects can be identical, but the order that properties are serialized in are not the same. Therefore the XML is different. DOH!

EDIT THREE: This does work. I am using it in my tests. But you must add items to collection properties in the order the code under test adds them.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Casey Burns
  • 1,223
  • 12
  • 15
0

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)

daf
  • 1,289
  • 11
  • 16
0

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.

Cristian Rusanu
  • 452
  • 5
  • 15
0

The Compare-Net-Objects project has built in test extensions to support comparing nested objects within NUnit.

using KellermanSoftware.CompareNetObjects;

[Test]
public void ShouldCompare_When_Equal_Should__Not_Throw_An_Exception()
{
    //Arrange
    string errorMessage = "Groups should be equal";
    var people1 = new List<Person>() { new Person() { Name = "Joe" } };
    var people2 = new List<Person>() { new Person() { Name = "Joe" } };
    var group1 = new KeyValuePair<string, List<Person>>("People", people1);
    var group2 = new KeyValuePair<string, List<Person>>("People", people2);

    //Assert
    group1.ShouldCompare(group2, errorMessage);
}
phoenix
  • 7,988
  • 6
  • 39
  • 45
-2

Stringify and compare two strings

Assert.AreEqual(JSON.stringify(LeftObject), JSON.stringify(RightObject))

jmtt89
  • 292
  • 1
  • 2
  • 12