Home > Blockchain >  assert that all fields in 2 objects are the same c#
assert that all fields in 2 objects are the same c#

Time:04-08

I am doing unit testing, and basically want to check that the data that 2 objects hold is the same

Assert.AreEqual(object1, object2);   
Assert.IsTrue(object1.Equals(object2)); //this of course doesn't work

I am searching for the C# equivalent of assertJ

Assert.That(object1).isEqualToComparingFieldByField(object2)

CodePudding user response:

You could either use records (c# 9 ) or you have to override the Equals method (if you have access and you can change the objects that you're working with). Records example:

var point = new Point(3, 4);
var point2 = new Point(3, 4);

var test = point.Equals(point2); //this is true

public record Point(int X, int Y);

with classes:

public class Point
{
    public int X { get; }
    public int Y { get; }


    public override bool Equals(object? obj)
    {
        if (obj == null)
         return false;

        return obj is Point point && (point.X == X && point.Y == Y);
    }

    public override int GetHashCode()
    {
        return HashCode.Combine(X, Y);
    }
}

if you are not allowed to touch the implementation, then you could use serialization and compare the strings:

var obj1Str = JsonConvert.SerializeObject(object1);
var obj2Str = JsonConvert.SerializeObject(object2);
Assert.Equal(obj1Str, obj2Str);

using Newtonsoft.Json nuget

CodePudding user response:

C# classes are reference equality, which means that variables are the same using the standard Equals and == if they point to the same object, you could override that behaivour, but it may break something now or in the future.

Or, you could switch to using a construct that's value equality by default, which structs as well as record classes are. If you can't (or don't want to) do that you can implement a value equals "helper" method yourself. I would not recommend overriding the Equals method or the == operator, as that can (and most likely will) lead to errors in the future instead I recommend you write your own ValueEquals method or extension method, something along the lines of

class Foo
{
    public int Count {get; set;}

    public string Message {get; set;}
}

public static bool ValueEquals(this Foo self, Foo other)
{
    return self.Count == other.Count && self.Message == other.Message;
}

public void MyTest()
{
    // Arrange and Act
    ...

    // Assert
    Assert.IsTrue(myFoo1.ValueEquals(myFoo2));
}

Depending on whether or not you can/ want to add a ValueEquals to your Foo class you can decide on doing it with an extension method or a normal method.

You could also implement a IEqualityComparer<T> like

public class FooValueEqualityComparer : IEqualityComparer<Foo>
{
    public bool Equals(Foo foo1, Foo foo2)
    {
        return foo1.Count == foo2.Count &&
               foo1.Message == foo2.Message;
    }

    public int GetHashCode(Foo foo)
    {
        return foo.GetHashCode();
    }
}

// Use it
public void MyTest()
{
    // Arrange and Act
    ...

    // Assert
    Assert.IsTrue(new FooEqualityComparer().Equals(myFoo1, myFoo2));
}


Or, you could write a generic ValueEquals that works for all^* classes using Reflection:

public static class ValueEqualityComparer
{
    public static bool ValueEquals<T>(this T self, T other) where T : class
    {
        var type = self.GetType();
        if (type == typeof(string))
            return self.Equals(other);

        var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);

        foreach (var property in properties)
        {
            var selfValue = property.GetValue(self);
            var otherValue = property.GetValue(other);

            // String is special, it's not primitive but is value equality like primitives
            if (property.PropertyType.IsPrimitive || property.PropertyType == typeof(string))
            {
                if (!selfValue.Equals(otherValue))
                    return false;
            }
            // If the property is a List value equals each member
            // Maybe find another type that allows indexing and is less restrictive
            else if (typeof(IEnumerable).IsAssignableFrom(property.PropertyType))
            {
                var selfList = ((IEnumerable)property.GetValue(self)).Cast<object>();
                var otherList = ((IEnumerable)property.GetValue(other)).Cast<object>();

                try
                {
                    // Using EquiZip from MoreLinq: https://github.com/morelinq/MoreLINQ/blob/master/MoreLinq/EquiZip.cs
                    foreach (var element in selfList.EquiZip(otherList, (selfItem, otherItem) => new { selfItem, otherItem }))
                    {
                        if (!ValueEquals(element.selfItem, element.otherItem))
                            return false;
                    }
                }
                catch (InvalidOperationException)
                {
                    // MoreLINQ throws a InvalidOperationException if our two enumerables aren't the same length
                    return false;
                }
            }
            else
            {
                if (!ValueEquals(selfValue, otherValue))
                    return false;
            }
        }

        return true;
    }
}

This implementation is by no means perfect, and should honestly only be used for UnitTests and also should be thoroughly tested itself. You can see my tests as a dotnetfiddle here

Or you could do it "dirty" and serialize the objects to a string and compare those values.

  • Related