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.