Unit Testing in .NET Core - Better Assertions with FluentAssertions and Shouldly

This is the fourth post in our Unit Testing in .NET Core series. In the previous post, we looked at parameterized unit testing. In this blog post, we will explore how to enhance your assertion capabilities in .NET Core unit testing, using the xUnit testing library. We'll start by examining the xUnit's built-in assertion methods and then introduce two alternative assertion libraries: FluentAssertions and Shouldly.

What are Assertions?

Assertions are the gatekeepers of unit testing. They are statements that express the expected behavior of your code. When you write unit tests, assertions are used to check whether the actual output of a unit matches your predefined expectations. If they don't align, the test fails, signaling a potential issue in your code.

Assertions typically consist of two components:

  • Actual Result: The output produced by executing the code being tested.
  • Expected Result: The predefined outcome you anticipate based on the input or conditions provided to the code.

Here is a simple example:

// Arrange (Setup)
int result = Calculator.Add(2, 3);
// Act (Invoke the code under test)
int expected = 5;
// Assert (Check if the actual result matches the expected result)
Assert.Equal(expected, result);

In this example, the assertion Assert.Equal(expected, result) checks whether the result of Calculator.Add(2, 3) equals the expected value of 5.

xUnit's Assert class

xUnit comes with a built-in class named Assert with a lot of methods to help in writing assertions in our unit tests. Let's take the example of Divide method in a Calculator class for checking the xUnit assertion options.

public class Calculator
{
    public int Divide(int dividend, int divisor)
    {
        if (divisor == 0)
        {
            throw new ArgumentException("Divisor cannot be zero.");
        }

        return dividend / divisor;
    }
}

The corresponding unit test will be the following

public class CalculatorTests
{
    [Fact]
    public void Divide_PositiveNumbers_ReturnsCorrectResult()
    {
        // Arrange
        var calculator = new Calculator();
        // Act
        int result = calculator.Divide(10, 2);
        // Assert
        Assert.Equal(5, result);
    }

    [Fact]
    public void Divide_DivisorIsZero_ThrowsException()
    {
        // Arrange
        var calculator = new Calculator();
        // Act and Assert
        Assert.Throws<ArgumentException>(() => calculator.Divide(10, 0));
    }
}

In the first method, the assertion statement checks whether the result is equal to 5. In the second method, the assertion checks whether the method throws an exception when divided by zero.

While the previous unit tests work with the default assertion library, the assertion statements lack readability. Wouldn't it be more user-friendly if our assertion statements were expressed in a more human-readable manner, such as saying 'the result should equal 5' instead of writing it as Assert.Equal(5, result)? There are alternative libraries available that can significantly improve the readability of assertion statements. Let's look at two of such libraries.

FluentAssertions: Enhancing Readability and Expressiveness

FluentAssertions is a popular assertion library that addresses the readability and expressiveness limitations of xUnit's built-in assertions. It provides a fluent and intuitive syntax for writing assertions, making your test cases more self-explanatory.

Before using FluentAssertions you will have to add the FluentAssertions library from Nuget.

  1. In Visual Studio, right-click on your test project in the Solution Explorer and select "Manage NuGet Packages."
  2. Search for "FluentAssertions" in the NuGet Package Manager and click "Install" to add it to your project.

If you are using Visual Studio Code you can make use of the .NET CLI to install the package. Open a terminal or command prompt and navigate to your project's directory. Then, run the following command:

dotnet add package FluentAssertions

Let's rewrite the previous test case using Fluent Assertions:

using Xunit;
using FluentAssertions;

public class CalculatorTests
{
    [Fact]
    public void Divide_PositiveNumbers_ReturnsCorrectResult()
    {
        // Arrange
        var calculator = new Calculator();
        // Act
        int result = calculator.Divide(10, 2);
        // Assert
        result.Should().Be(5);
    }

    [Fact]
    public void Divide_DivisorIsZero_ThrowsException()
    {
        // Arrange
        var calculator = new Calculator();
        // Act and Assert
        Action action = () => calculator.Divide(10, 0);
        action.Should().Throw<ArgumentException>().WithMessage("Divisor cannot be zero.");
    }
}

In the updated tests, we've replaced the traditional xUnit assertions (Assert.Equal and Assert.Throws) with Fluent Assertions methods (Should().Be and Should().Throw). FluentAssertions provides a more fluent and expressive way to write assertions.

Using FluentAssertions can make your unit tests easier to read and understand, especially when you have complex assertions or multiple conditions to check. It provides a more natural and expressive way to express your expectations about the code under test.

Shouldly: A Different Approach to Readability

Shouldly is another assertion library for .NET that offers a unique approach to readability and expressiveness.

Before using Shouldly you will have to add the Shouldly library from Nuget.

  1. In Visual Studio, right-click on your test project in the Solution Explorer and select "Manage NuGet Packages."
  2. Search for "Shouldly" in the NuGet Package Manager and click "Install" to add it to your project.

If you are using Visual Studio Code you can make use of the .NET CLI to install the package. Open a terminal or command prompt and navigate to your project's directory. Then, run the following command:

dotnet add package Shouldly

Let's rewrite the previous test case using Shouldly:

using Xunit;
using Shouldly;

public class CalculatorTests
{
    [Fact]
    public void Divide_PositiveNumbers_ReturnsCorrectResult()
    {
        // Arrange
        var calculator = new Calculator();
        // Act
        int result = calculator.Divide(10, 2);
        // Assert
        result.ShouldBe(5);
    }

    [Fact]
    public void Divide_DivisorIsZero_ThrowsException()
    {
        // Arrange
        var calculator = new Calculator();
        // Act and Assert
        Should.Throw<ArgumentException>(() => calculator.Divide(10, 0)).Message.ShouldBe("Divisor cannot be zero.");
    }
}

In the updated tests, we've used Shouldly's ShouldBe and Should.Throw methods to express our assertions.

Comparison between xUnit's Assert, FluentAssertions and Shouldly

Here's a table comparing commonly used assertion methods in xUnit's Assert, FluentAssertions, and Shouldly libraries:

Assertion Method xUnit (Assert) FluentAssertions Shouldly
Equality Assert.Equal(expected, actual) actual.Should().Be(expected) actual.ShouldBe(expected)
Inequality Assert.NotEqual(expected, actual) actual.Should().NotBe(expected) actual.ShouldNotBe(expected)
Null Reference Assert.Null(actual) actual.Should().BeNull() actual.ShouldBeNull()
Not Null Reference Assert.NotNull(actual) actual.Should().NotBeNull() actual.ShouldNotBeNull()
True Condition Assert.True(condition) condition.Should().BeTrue() condition.ShouldBeTrue()
False Condition Assert.False(condition) condition.Should().BeFalse() condition.ShouldBeFalse()
Floating-Point Equality Assert.Equal(expected, actual, precision) actual.Should().BeApproximately(expected, precision) actual.ShouldBe(expected, tolerance)
Collection Count Assert.Equal(expectedCount, collection.Count()) collection.Should().HaveCount(expectedCount) collection.ShouldHaveCount(expectedCount)
Collection Contents Assert.Contains(expectedItem, collection) collection.Should().Contain(expectedItem) collection.ShouldContain(expectedItem)
Exception Thrown Assert.Throws<ExceptionType>(() => CodeUnderTest()) Action act = () => CodeUnderTest(); act.Should().Throw<ExceptionType>() Should.Throw<ExceptionType>(() => CodeUnderTest())


Summary

In the world of .NET Core unit testing, choosing the right assertion library can significantly improve the readability and expressiveness of your test cases. While xUnit provides basic assertion methods, both FluentAssertions and Shouldly offer more powerful and readable alternatives. Your choice between the two will depend on your team's coding style and preferences. Try out both and select the one that enhances your testing experience and makes your tests more understandable.

References