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.
In Visual Studio, right-click on your test project in the Solution Explorer and select "Manage NuGet Packages."
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.
In Visual Studio, right-click on your test project in the Solution Explorer and select "Manage NuGet Packages."
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
Subscribe to my newsletter
Read articles from Geo J Thachankary directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by