C# – Use FluentAssertions to improve unit tests

FluentAssertions is a library that improves unit tests by providing better failure messages, simplifies assertions in many scenarios, and provides a fluent interface (which improves code readability).

In this article, I’ll show a few examples of how FluentAssertions can improve unit tests by comparing it with the built-in assertions (from Microsoft.VisualStudio.TestTools.UnitTesting).

Install FluentAssertions

Add the FluentAssertions package to your unit test project (View > Other Windows > Package Manager Console) by executing:

Install-Package FluentAssertions
Code language: PowerShell (powershell)

FluentAssertions is basically a bunch of extension methods that you can use in your unit tests. I’ll show examples of using it throughout this article.

FluentAssertions provides better failure messages

When unit tests fail, they show a failure message. Ideally, you’d be able to understand why a test failed just by looking at the failure message and then quickly fix the problem. This is one of the key benefits of using FluentAssertions: it shows much better failure messages compared to the built-in assertions.

I’ll compare the failure messages below.

The following test uses the built-in assertions to check if the two references are pointing to the same object:

[TestMethod()]
public void DeepCopyTest_ReferencesArentCopied()
{
	//arrange
	var team = GetTeam();
	var objectCopier = new ObjectCopier();

	//act
	var copy = (NFLTeam)objectCopier.DeepCopy(team);

	//assert
	Assert.AreNotSame(team.HeadCoach, copy.HeadCoach);
}
Code language: C# (cs)

Here’s the unit test failure message:

Assert.AreNotSame failedCode language: plaintext (plaintext)

Compare this with the FluentAssertions equivalent using Should().NotBeSameAs():

using FluentAssertions;

[TestMethod()]
public void DeepCopyTest_ReferencesArentCopied()
{
	//arrange
	var team = GetTeam();
	var objectCopier = new ObjectCopier();

	//act
	var copy = (NFLTeam)objectCopier.DeepCopy(team);

	//assert
	team.HeadCoach.Should().NotBeSameAs(copy.HeadCoach);
}
Code language: C# (cs)

Here’s the unit test failure message:

Did not expect team.HeadCoach to refer to 


CopyingObjects.Person
{
   FirstName = 
"Dan"
   LastName = 
"Campbell"
}.Code language: plaintext (plaintext)

Compared with the built-in assertion failure message, this is a great failure message that explains why the test failed (team.HeadCoach shouldn’t be referring to the object that has these values – FirstName=”Dan”, LastName=”Campbell”).

FluentAssertions simplifies asserting object equality

Two objects are equal if their public properties have equal values (this is the usual definition of object equality). If you’re using the built-in assertions, then there are two ways to assert object equality. One way involves overriding and implementing Equals(object o) in your class. The other way is to assert that the properties are the same – one assertion per property – like this:

[TestMethod()]
public void DeepCopyTest_ValuesAreCopied()
{
	//arrange
	var team = GetTeam();
	var objectCopier = new ObjectCopier();

	//act
	var copy = (NFLTeam)objectCopier.DeepCopy(team);

	//assert
	Assert.AreEqual(team.HeadCoach.FirstName, copy.HeadCoach.FirstName);
	Assert.AreEqual(team.HeadCoach.LastName, copy.HeadCoach.LastName);
}
Code language: C# (cs)

Note: I’d suggest using built-in StringAssert when asserting against strings (instead of Assert.AreEqual).

When the unit test fails, it’ll show the following failure message:

Assert.AreEqual failed. Expected:<Dan>. Actual:<Matt>. Code language: plaintext (plaintext)

This message is nice and clear, but notice it didn’t even run the second assert? The unit test stopped once the first assert failed. That means you will have to fix one failing assertion at a time, re-run the test, and then potentially fix other failing assertions.

Now compare this with the FluentAssertions way to assert object equality:

using FluentAssertions;

[TestMethod()]
public void DeepCopyTest_ValuesAreCopied()
{
	//arrange
	var team = GetTeam();
	var objectCopier = new ObjectCopier();

	//act
	var copy = (NFLTeam)objectCopier.DeepCopy(team);

	//assert
	team.HeadCoach.Should().BeEquivalentTo(copy.HeadCoach);
}
Code language: C# (cs)

Note: Use Should().Be() if you’re asserting object’s that have overridden Equals(object o), or if you’re asserting values.

First, notice that there’s only a single call to Should().BeEquivalentTo(). FluentAssertions walks the object graph and asserts the values for each property. This is much better than needing one assertion for each property. Also, you don’t have to override Equals(object o) to get this functionality.

Second, take a look at the unit test failure message:

Expected member FirstName to be "Matt" with a length of 4, but "Dan" has a length of 3, differs near "Dan" (index 0).
Expected member LastName to be "Patricia", but "Campbell" differs near "Cam" (index 0).Code language: plaintext (plaintext)

Notice that it gave results for all properties that didn’t have equal values. This is much better than how the built-in assertions work, because you can see all the problems at once.

Asserting the equality of a subset of the object’s properties

What if you want to only compare a few of the properties for equality?

The simplest way to do that is to select the properties into an anonymous type and assert against it, like this:

using FluentAssertions;

[TestMethod()]
public void DeepCopyTest_CopiesPlayerIdentity()
{
	//arrange
	var player = new Player()
	{
		FirstName = "Matthew",
		LastName = "Stafford",
		Position = PlayerPositions.QB,
		YearsOfExperience = 12,
		College = "Georgia"
	};
	var objectCopier = new ObjectCopier();

	//act
	var copy = (Player)objectCopier.DeepCopy(player);

	//assert
	new { player.FirstName, player.LastName, player.Position }
	 .Should()
	 .BeEquivalentTo(new { copy.FirstName, copy.LastName, copy.Position });
}
Code language: C# (cs)

When this unit test fails, it gives a very clear failure message:

Expected member FirstName to be "Todd" with a length of 4, but "Matthew" has a length of 7, differs near "Mat" (index 0).
Expected member Position to equal PlayerPositions.RB(1) by value, but found PlayerPositions.QB(0).Code language: plaintext (plaintext)

You may be wondering, why not use the built-in assertions since there’s only a few properties? You could do that. The trouble is the first assertion to fail prevents all the other assertions from running. If multiple assertions are failing, you’d have to run the test repeatedly and fix one problem at a time.

Note: The FluentAssertions documentation says to use EquivalencyAssertionOptions.Including() (one call per property to include) to specify which properties to include, but I wasn’t able to get that working.

Asserting a single property

If you’re only asserting the value of a single property, keep it simple and assert the property directly (instead of using the approach shown in the previous section), like this:

[TestMethod()]
public void DeepCopyTest_CopiesPlayerFirstName()
{
	//arrange
	var player = new Player()
	{
		FirstName = "Matthew",
		LastName = "Stafford",
		Position = PlayerPositions.QB,
		YearsOfExperience = 12,
		College = "Georgia"
	};
	var objectCopier = new ObjectCopier();

	//act
	var copy = (Player)objectCopier.DeepCopy(player);

	//assert
	copy.FirstName.Should().Be(player.FirstName);
}
Code language: C# (cs)

FluentAssertions allows you to chain assertions

It’s typically a good idea to only assert one thing in a unit test, but sometimes it makes sense to assert multiple things. This can reduce the number of unit tests.

For example, let’s say you want to test the DeepCopy() method. It takes an object and returns a deep copy of that object, meaning it has all the same values, but doesn’t share any of the same references. You could have two different unit tests – one that tests that the values are copied and one that tests that the references aren’t copied. Why not combine that into a single test?

Here is a unit test that uses the built-in assertions to verify the output of the DeepCopy() method:

[TestMethod()]
public void DeepCopyTest_ValuesAreCopied_ButReferencesArentCopied()
{
	//arrange
	var team = GetTeam();
	var objectCopier = new ObjectCopier();

	//act
	var copy = (NFLTeam)objectCopier.DeepCopy(team);

	//assert
	Assert.AreNotSame(team.HeadCoach, copy.HeadCoach);
	Assert.AreEqual(team.HeadCoach.FirstName, copy.HeadCoach.FirstName);
	Assert.AreEqual(team.HeadCoach.LastName, copy.HeadCoach.LastName); 
}
Code language: C# (cs)

Compare this with the FluentAssertions equivalent, which chains together assertions:

using FluentAssertions;

[TestMethod()]
public void DeepCopyTest_ValuesAreCopied_ButReferencesArentCopied()
{
	//arrange
	var team = GetTeam();
	var objectCopier = new ObjectCopier();

	//act
	var copy = (NFLTeam)objectCopier.DeepCopy(team);

	//assert
	team.HeadCoach.Should().NotBeSameAs(copy.HeadCoach).And.BeEquivalentTo(copy.HeadCoach);
}
Code language: C# (cs)

FluentAssertions provides a fluent interface (hence the ‘fluent’ in the name), allowing you chain method calls together. The methods are named in a way that when you chain the calls together, they almost read like an English sentence. This is meant to maximize code readability.

Leave a Comment