XUnit Assertion for checking equality of objects
Asked Answered
I

7

80

I am using XUnit framework to test my C# code.

Is there any assert method available in this framework which does the object comparison? My intention is to check for equality of each of the object's public and private member variables.

I tried those alternatives but seldom it works:

1) bool IsEqual = (Obj1 == Obj2)
2) Assert.Same(Obj1, Obj2) which I couldnt understand what happens internally
Isabelisabelita answered 21/6, 2012 at 9:31 Comment(2)
There is "deep comparison" in xUnit. You'll have to implement IEquatable<T> for your objects, and then Assert.Equals will work.Tyratyrannical
Assert.Same() compares by reference; it asserts that Obj1 and Obj2 are the same object rather than just looking the same.Anatomize
S
23

You need to have a custom comparer to achieve this, when you compare objects otherwise they are checked on the basis of whether they are referring to the same object in memory. To override this behavior you need to override the Equals and GetHashCode method and then you could do:

Assert.True(obj1.Equals(obj2));

Here is an MSDN page abt overloading Equals method: http://msdn.microsoft.com/en-us/library/ms173147(v=vs.80).aspx

Also apt the comment on the question: What's the difference between IEquatable and just overriding Object.Equals()?

Sublieutenant answered 21/6, 2012 at 9:40 Comment(6)
I understand that by implementing custom "Equals" method, this check can be performed. But is there any method to do a blind byte comparison, which will make the check easier? This is because I will end up having an "Equals" implementation in "Software under test" just for unit testing sake.Isabelisabelita
I tried using serialization methods to convert both objects to a byte array and it worked. But it comes with a constraint of adding [serializable] attribute to my class which has private member variables. I guess this is not good in design perspectiveIsabelisabelita
I am against overriding these two methods just for unit tests.Kowtko
And what happens when your logic for business rules equality differs from your logic for test equality?Elemental
you just need to pass an IEqualityComparer as the third argument ` Assert.Equal(expectedCar, actualCar, CarComparer); `Heriot
This answer is only useful as long as the link works (which it no longer does).Aliunde
M
109

I had similar issue, but then luckily I am already using

using Newtonsoft.Json;

So I just had to serialize it to json object then compare as string.

var obj1Str = JsonConvert.SerializeObject(obj1);
var obj2Str = JsonConvert.SerializeObject(obj2);
Assert.Equal(obj1Str, obj2Str );
Mou answered 2/5, 2017 at 21:4 Comment(6)
I consider this more useful than implementing an Equals method because I'd like my assert failure to tell me something about what was wrong. What I'd like ideally is to have something that can traverse an object tree and accumulate information about which properties/subtrees are non-equal and fail with that information.Charismatic
@RikkiGibson isn't implementing Equals just the right way to do it? Just add exceptions to a list in the equals for every property that is different, then at the end either return true or throw all the exceptionsSukin
Yes, but it can be painstaking to do case by case, which is why I’ve tended to look for reflection based solutions in the past when comparing trees of plain old objects, primitives and collections.Charismatic
@RikkiGibson There are some NuGet packages that do what you want. See my answer.Kowtko
There is no warranty that these objects will be serialized the same way, there is some risk here.Gharry
There is now Assert.Equivalent. See Adam T's answer.Alcantara
B
44

As of Xunit 2.4.2, released 1 Aug 2022, this feature is implemented as Assert.Equivalent.

Example:

Assert.Equivalent(Obj1, Obj2);

or

Assert.Equivalent(Obj1, Obj2, strict:true)

See release notes: https://xunit.net/releases/v2/2.4.2

Blood answered 17/10, 2022 at 15:28 Comment(3)
The release note link currently: xunit.net/releases/v2/2.4.2Noise
Unfortunately it fails when checking collections items positionCabbage
Beautiful! Exactly what was needed in my case. Thanks!Carpeting
P
39

FluentAssertions library has some pretty powerful comparison logic inside.

myObject.ShouldBeEquivalentTo(new { SomeProperty = "abc", SomeOtherProperty = 23 });

You can even use this to assert on part of "myObject". However, it might not help you with the private fields.

Pitchblende answered 29/8, 2019 at 9:35 Comment(2)
The current version of the library seem to using the following API: actual.Should().BeEquivalentTo(expected);Medawar
Out from under my rock I crawl, face the xUnit test monster I must... It makes so little sense, says I. My inner coding child always had to be reminded that unit test tantrums are so totally not age appropriate. Well, that was before I discovered FluentAssertions. That day was today. And you, @AdrianPetrescu have the heartfelt appreciation of this bleary-eyed developer.Substituent
S
23

You need to have a custom comparer to achieve this, when you compare objects otherwise they are checked on the basis of whether they are referring to the same object in memory. To override this behavior you need to override the Equals and GetHashCode method and then you could do:

Assert.True(obj1.Equals(obj2));

Here is an MSDN page abt overloading Equals method: http://msdn.microsoft.com/en-us/library/ms173147(v=vs.80).aspx

Also apt the comment on the question: What's the difference between IEquatable and just overriding Object.Equals()?

Sublieutenant answered 21/6, 2012 at 9:40 Comment(6)
I understand that by implementing custom "Equals" method, this check can be performed. But is there any method to do a blind byte comparison, which will make the check easier? This is because I will end up having an "Equals" implementation in "Software under test" just for unit testing sake.Isabelisabelita
I tried using serialization methods to convert both objects to a byte array and it worked. But it comes with a constraint of adding [serializable] attribute to my class which has private member variables. I guess this is not good in design perspectiveIsabelisabelita
I am against overriding these two methods just for unit tests.Kowtko
And what happens when your logic for business rules equality differs from your logic for test equality?Elemental
you just need to pass an IEqualityComparer as the third argument ` Assert.Equal(expectedCar, actualCar, CarComparer); `Heriot
This answer is only useful as long as the link works (which it no longer does).Aliunde
K
20

There are NuGet packages that do this for you. Here are two examples that I personally use.

  1. DeepEqual:

    object1.ShouldDeepEqual(object2);
    
  2. ExpectedObjects:

    [Fact]
    public void RetrievingACustomer_ShouldReturnTheExpectedCustomer()
    {
      // Arrange
      var expectedCustomer = new Customer
      {
        FirstName = "Silence",
        LastName = "Dogood",
        Address = new Address
        {
          AddressLineOne = "The New-England Courant",
          AddressLineTwo = "3 Queen Street",
          City = "Boston",
          State = "MA",
          PostalCode = "02114"
        }                                            
      }.ToExpectedObject();
    
    
      // Act
      var actualCustomer = new CustomerService().GetCustomerByName("Silence", "Dogood");
    
      // Assert
      expectedCustomer.ShouldEqual(actualCustomer);
    }
    
Kowtko answered 17/8, 2018 at 11:7 Comment(2)
Any opinions on pros/cons of these libraries?Fendley
@WillP. DeepEqual doesn't have official .NET Standard/Core support (yet). It works but it could cause issues. Otherwise they're pretty much the same. ExpectedObjects has a few more features though like Partial or Custom Comparisons. That's why I use ExpectedObjects in pretty much all of my projects nowadays but it's more a personal preference.Kowtko
S
10

I know this is an old question, but since I stumbled upon it I figured I'd weigh in with a new solution that's available (at least in xunit 2.3.1 in a .net Core 2.0 solution).

I'm not sure when it was introduced, but there is now an overloaded form of .Equal that accepts an instance of IEqualityComparer<T> as the third parameter. You can create a custom comparer in your unit test without polluting your code with it.

The following code can be invoked like this: Assert.Equal(expectedParameters, parameters, new CustomComparer<ParameterValue>());

XUnit natively appears to stop processing a test as soon as a failure is encountered, so throwing a new EqualException from within our comparer seems to be in line with how XUnit works out of the box.

public class CustomComparer<T> : IEqualityComparer<T>
{
    public bool Equals(T expected, T actual)
    {
        var props = typeof(T).GetProperties(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance);
        foreach (var prop in props)
        {
            var expectedValue = prop.GetValue(expected, null);
            var actualValue = prop.GetValue(actual, null);
            if (!expectedValue.Equals(actualValue))
            {
                throw new EqualException($"A value of \"{expectedValue}\" for property \"{prop.Name}\"",
                    $"A value of \"{actualValue}\" for property \"{prop.Name}\"");
            }
        }

        return true;
    }

    public int GetHashCode(T parameterValue)
    {
        return Tuple.Create(parameterValue).GetHashCode();
    }
}

Edit: I found that comparing the actual and expected values with != was not effective for certain types (I'm sure there's a better explanation involving the difference between reference types and value types, but that's not for today). I updated the code to use the .Equals method to compare the two values and that seems to work much better.

Seaport answered 13/4, 2018 at 20:59 Comment(5)
This works but I'd rather just use a NuGet package that does a similar thing. Less code that I have to worry aboutKowtko
You should not throw on the Equals. It can screw up Assert.DoesNotContain since it expects to get false on each of the entries. You better return false instead.Barmecidal
@Kowtko less code indeed, but a dependency on an external package, and all the cyber risks that go with it...Affirmative
Since this answer continues to get upvotes more than 3 years later, I'll go ahead and point out that I created an open source NuGet package to do this and it's a lot more robust than what I've included here. Source: github.com/engineer-andrew/ea-xunit-helpers Package: nuget.org/packages/EAXUnitHelpers.ComparisonSeaport
@Affirmative Or if you don't want an external package: Make your own function so that the code is encapsulated and easier to use instead of always having to use a CustomComparer.Kowtko
B
1

Here is another implementation of the IEqualityComparer interface with a really GenericComparer

And for asserting that 2 lists of same type are equal, regardless of the list container, you can add this extension:

public static bool IsEqualTo<T>(this IEnumerable<T> list, IEnumerable<T> expected) where T : class
{
    if (!list.HasSameLengthThan<T>(expected))
        return false;

    Assert.Equal(expected, list, new GenericComparer<T>());
    return true;
}
Backboard answered 10/2, 2022 at 10:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.