FluentAssertions: Make Your Unit Tests Easier to Write and Understand

September 29th 2014 FluentAssertions Unit Testing

How often it happens to you, that you need to write "too much" supporting code for you unit tests? Let's take a look at the following example:

[Test]
public void CompareDtoAndBusinessClassesBefore()
{
    // arrange
    UserEntity[] expectedUsers = new[]
        {
            new UserEntity(1, "Dennis", "Doomen", "ddoomen"),
            new UserEntity(2, "Charlie", "Poole", "cpoole")
        };
    var repository = new Repository();
    repository.Users.AddRange(expectedUsers);

    // act
    var adminService = new AdminService(repository);
    IEnumerable<UserDto> actualUsers = adminService.GetAllUsers();

    // assert
    var sortedExpectedUsers = expectedUsers.OrderBy(user => user.Username);
    var sortedActualUsers = actualUsers.OrderBy(user => user.Username);
    CollectionAssert.AreEqual(sortedExpectedUsers, sortedActualUsers, 
        new UserComparer());
}

It represents a typical scenario when testing service oriented applications. You can notice two different classes for describing users:

  • UserDto is exposed to the consumers of the service
  • UserEntity is being used inside the application and will usually have more properties than the former one.

Both of them implement a common interface IUser to make testing easier and maybe even enable some code sharing elsewhere in the project.

While the arrange and act part could hardly be more concise, there is a lot of plumbing code in the assert part of the test. UserComparer probably won't be used outside the tests, but is required for comparing instance of IUser whether they are of the same type or not. On top of that; we are sorting both collections because results are not ordered. We should be using CollectionAssert.AreEquivalent instead of CollectionAssert.AreEqual here, but there's no overload accepting an IComparer available, so we had to find a different solution.

The above sample is using NUnit test framework, but the test wouldn't turn out much differently, if we were using any other test framework. The additional code we need to write has many disadvantages: it means extra work, there can be bugs in the plumbing code, and the tests are more difficult to understand.

While trying to simplify a particularly nasty case of the above described scenario, I stumbled across FluentAssertions NuGet package and I've quickly grown to like it. This is how the above test could look like, when using its assertions:

[Test]
public void CompareDtoAndBusinessClassesAfter()
{
    // arrange
    UserEntity[] expectedUsers = new[]
        {
            new UserEntity(1, "Dennis", "Doomen", "ddoomen"),
            new UserEntity(2, "Charlie", "Poole", "cpoole")
        };
    var repository = new Repository();
    repository.Users.AddRange(expectedUsers);

    // act
    var adminService = new AdminService(repository);
    IEnumerable<UserDto> actualUsers = adminService.GetAllUsers();

    // assert
    actualUsers.ShouldAllBeEquivalentTo(expectedUsers);
}

Of course, the arrange and act parts haven't changed. The assert part shrunk down to a single line with no supporting code whatsoever. Although the assertion is named clearly enough, it's you need to know the following to understand the test fully:

  • The equivalency of both collections is asserted, i.e. the order is not important, hence there's no sorting required.
  • The items in both collections are compared property by property, based on their names, therefore the IUser interface is not required any more, as long as properties have the same name.

There's another advantage in using FluentAssertions that can't be noticed by just looking at the test code. If we introduce a bug in the service method, this is the output of the first test:

  Expected is <System.Linq.OrderedEnumerable`2 
[FluentAssertionsForCollections.UserEntity,System.String]>, 
actual is <System.Collections.Generic.List`1 
[FluentAssertionsForCollections.UserDto]> with 2 elements
  Values differ at index [0]
  Expected: <FluentAssertionsForCollections.UserEntity>
  But was:  <FluentAssertionsForCollections.UserDto>

Yes, we could improve it by overriding ToString() in both classes, but that would mean even more supporting code for the sake of test, which we want to avoid.

Here's the output of the second test:

Expected item[0].Id to be 1, but found 2.
Expected item[0].Id to be 1, but found 2.
Expected item[1].Id to be 2, but found 3.

With configuration:
- Include only the declared properties
- Match property by name (or throw)

There is still room for improvement, but unlike the first case; it's quite obvious how both collections differ. This is just another argument in favor of FluentAssertions.

Still, I've just scratched the surface. The library includes a huge collection of assertions which should have you covered, once you get to know them all. If not, it's really easy customize the behavior of many of them with no or next to none supporting code.

There's really no reason not to give it a try. It seems to work with all unit test frameworks currently available. You never know, it might have a big impact on the way you're writing unit tests.

Copyright
Creative Commons License