PROGRAMMING

Several ways to Compare two C# objects while Unit Testing

Shiljo Paulson
5 min readAug 26, 2021
Photo by NordWood Themes on Unsplash

So as per dictionary compare means

estimate, measure, or note the similarity or dissimilarity between.

Every software engineer would have come across comparing two objects/instances of same type. Today, will discuss how I was comparing two objects of same type in my initial days of my career then later figured out that there is better & easier solution. So, let’s see how it all started and can be done from beginner to pro. Those who are well verse in C# you can go directly to Solution 4 (Fluent Assertions).

Here is my class

public class Employee
{
public int Id { get; set; }

public string Code { get; set; }

public string FirstName { get; set; }

public string MiddleName { get; set; }

public string LastName { get; set; }

public DateTime DateOfBirth { get; set; }

public string Email { get; set; }
}

here is the dumbest ever comparison I have ever seen & I have done that too in my earlier days of my career.

public void ShouldBeEqual()
{
// Arrange
var expected = new Employee
{
Id = 1,
Code = "E01",
FirstName = "Shawn",
MiddleName = "A",
LastName = "Paul",
DateOfBirth = new DateTime(2002, 12, 1),
Email = "shawn.paul@xyz.com"
};
var actual = new Employee
{
Id = 1,
Code = "E01",
FirstName = "Shawn",
MiddleName = "A",
LastName = "Paul",
DateOfBirth = new DateTime(2002, 12, 1),
Email = "shawn.paul@xyz.com"
};

// Assert
Assert.AreEqual(expected.Id, actual.Id);
Assert.AreEqual(expected.Code, actual.Code);
Assert.AreEqual(expected.FirstName, actual.FirstName);
Assert.AreEqual(expected.MiddleName, actual.MiddleName);
Assert.AreEqual(expected.LastName, actual.LastName);
Assert.AreEqual(expected.DateOfBirth, actual.DateOfBirth);
Assert.AreEqual(expected.Email, actual.Email);
}

and this unit test will pass because all property values are the same between two objects. Here we are testing all the properties individually which is not an efficient way to do so why not try asserting both objects like

Assert.AreEqual(expected, actual);

public void ShouldBeEqual()
{
// Arrange
var expected = new Employee
{
Id = 1,
Code = "E01",
FirstName = "Shawn",
MiddleName = "A",
LastName = "Paul",
DateOfBirth = new DateTime(2002, 12, 1),
Email = "shawn.paul@xyz.com"
};
var actual = new Employee
{
Id = 1,
Code = "E01",
FirstName = "Shawn",
MiddleName = "A",
LastName = "Paul",
DateOfBirth = new DateTime(2002, 12, 1),
Email = "shawn.paul@xyz.com"
};

// Assert
Assert.AreEqual(expected, actual);
}

but this test method fails with message

Assert.AreEqual failed. Expected:<Employee>. Actual:<Employee>.

even though all the properties values are the same it fails. The reason it failed it because in c# by default a class gets inherited from System.Object which has its own methods like Equals(), GetHashCode(), ToString() and few more.

Assert.AreEqual(expected, actual)

will make use of Equals() method and we have not overridden/implemented it in our Employee class.

Solution 1 [Equals( )]

public class Employee
{
public int Id { get; set; }

public string Code { get; set; }

public string FirstName { get; set; }

public string MiddleName { get; set; }

public string LastName { get; set; }

public DateTime DateOfBirth { get; set; }

public string Email { get; set; }

public override bool Equals(object obj)
{
if (obj == null) { return false; }
if (object.ReferenceEquals(this, obj)) { return true; }

var employee = obj as Employee;
return this.Id == employee.Id
&& this.Code == employee.Code
&& this.FirstName == employee.FirstName
&& this.MiddleName == employee.MiddleName
&& this.LastName == employee.LastName
&& this.DateOfBirth == employee.DateOfBirth
&& this.Email == employee.Email;
}

public override int GetHashCode()
{
return $"{this.Id}{this.Code}{this.FirstName}{this.MiddleName}{this.LastName}{this.DateOfBirth}{this.Email}".GetHashCode();
}
}

Here we have implemented method Equals() and it requires GetHashCode() also to be overridden.

Now if we run the unit test it will pass.

Solution 2 [IEqualityComparer]

In the previous solution we implemented Equals() method hence we were able to compare two objects. What if it is a DTO (Data Transfer Object) or is third party/external library then we don’t have control over implementing Equals() method in it. We have a solution to it by making use of IEqualityComparer<T> Interface & below is the implementation

public class EmployeeComparer : IEqualityComparer<Employee>
{
public bool Equals(Employee x, Employee y)
{
if (x == null && y == null) { return true; }
else if(x == null || y == null) { return false; }
else if (object.ReferenceEquals(x, y)) { return true; }

return x.Id == y.Id
&& x.Code == y.Code
&& x.FirstName == y.FirstName
&& x.MiddleName == y.MiddleName
&& x.LastName == y.LastName
&& x.DateOfBirth == y.DateOfBirth
&& x.Email == y.Email;
}

public int GetHashCode([DisallowNull] Employee obj)
{
return $"{obj.Id}{obj.Code}{obj.FirstName}{obj.MiddleName}{obj.LastName}{obj.DateOfBirth}{obj.Email}".GetHashCode();
}
}

and now our unit test method will look like below

public void ShouldBeEqual()
{
// Arrange
var expected = new Employee
{
Id = 1,
Code = "E01",
FirstName = "Shawn",
MiddleName = "A",
LastName = "Paul",
DateOfBirth = new DateTime(2002, 12, 1),
Email = "shawn.paul@xyz.com"
};
var actual = new Employee
{
Id = 1,
Code = "E01",
FirstName = "Shawn",
MiddleName = "A",
LastName = "Paul",
DateOfBirth = new DateTime(2002, 12, 1),
Email = "shawn.paul@xyz.com"
};
var employeeComparer = new EmployeeComparer();

// Assert
Assert.IsTrue(employeeComparer.Equals(expected, actual));
}

and our unit test pass.

Solution 3 [Serialize]

I have also seen smart people avoid writing all these comparisons implementation codes and instead rely on JsonSerializers & XmlSerializers like below

public void ShouldBeEqual()
{
// Arrange
var expected = new Employee
{
Id = 1,
Code = "E01",
FirstName = "Shawn",
MiddleName = "A",
LastName = "Paul",
DateOfBirth = new DateTime(2002, 12, 1),
Email = "shawn.paul@xyz.com"
};
var actual = new Employee
{
Id = 1,
Code = "E01",
FirstName = "Shawn",
MiddleName = "A",
LastName = "Paul",
DateOfBirth = new DateTime(2002, 12, 1),
Email = "shawn.paul@xyz.com"
};

var serializer = new JavaScriptSerializer();
var expectedString = serializer.Serialize(expected);
var actualString = serializer.Serialize(actual);

// Assert
Assert.AreEqual(expectedString, actualString);
}

basically serialize to any string/text based format and then compare both strings.

Solution 4 [Fluent Assertion]

What if I tell you there is an even simpler way to compare two objects in unit tests without even writing any extra implementation code.

public void ShouldBeEqual()
{
// Arrange
var expected = new Employee
{
Id = 1,
Code = "E01",
FirstName = "Shawn",
MiddleName = "A",
LastName = "Paul",
DateOfBirth = new DateTime(2002, 12, 1),
Email = "shawn.paul@xyz.com"
};
var actual = new Employee
{
Id = 1,
Code = "E01",
FirstName = "Shawn",
MiddleName = "A",
LastName = "Paul",
DateOfBirth = new DateTime(2002, 12, 1),
Email = "shawn.paul@xyz.com"
};

// Assert
actual.Should().BeEquivalentTo(expected);
}

and then this test will pass, provided you must install NuGet package Fluent Assertions.

Let’s see a failed scenario and see what’s going to be error message

public void ShouldBeEqual()
{
var expected = new Employee
{
Code = "E01"
};
var actual = new Employee
{
Code = "E02"
};

actual.Should().BeEquivalentTo(expected);
}

Expected property actual.Code to be “E01”, but “E02” differs near “2” (index 2).

the above message is extremely readable as far I have ever seen.

You can do more with Fluent Assertions by customizing and to know more about object comparison have a look at https://fluentassertions.com/objectgraphs/

Yes, there are many other NuGet packages like Shouldly and many others.

Please share your valuable comments and feedback.

--

--

Shiljo Paulson
Shiljo Paulson

Written by Shiljo Paulson

System Design, Full stack Developer, good at OOPs, .Net, C#, TypeScript, JavaScript, SQL, HTML. Recent times interest is on Cloud, System Design and GoLang.

No responses yet