Exploring Fluent Assertions

  • Gérald Barré

Most common unit testing libraries come with their assertion library. Fluent Assertions is an alternative library which try to be better than the built-in assertion libraries.

#How to use Fluent Assertions

You can reference the FluentAssertions NuGet package and its analyzer:

  <ItemGroup>
    <PackageReference Include="FluentAssertions" Version="5.10.3" />
    <PackageReference Include="FluentAssertions.Analyzers" Version="0.11.4" />

    <PackageReference Include="xunit" Version="2.4.1" />
    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
  </ItemGroup>

Then, you should be able to write your first assertion:

using FluentAssertions;
using Xunit;

public class UnitTest1
{
    [Fact]
    public void Test()
    {
        int my_variable = 1;
        my_variable.Should().BeGreaterThan(0);
    }
}

#Assertion methods are numerous and discoverable

By making assertion discoverable, FluentAssertions helps you writing tests. Builtin assertions libraries often have all assert methods under the same static class. So, whatever the object you are asserting, all methods are available. FluentAssertions uses a specialized Should extension method to expose only the methods available for the type you are validating. This is very interesting to navigate across the numerous assertion methods.

Intellisense for a value of type Int32

Intellisense for a value of type string

Intellisense for a value of type collection

#Assertions are readable

This point is arguable of course. But I think the test code should be easy to read and the intent should be clear. Using Fluent Assertions:

  • Assertions are almost English sentences
  • All methods have an optional because parameter to explain the test
  • You can combine multiple assertions for the same subject using And
[Fact]
public void Test()
{
    string my_variable = "";

    my_variable.Should().Contain("abc")
        .And.MatchRegex("[0-9]+", because: "it must contain digits");

    Action action = () => throw new CustomException("sample exception");
    action.Should().Throw<RuleViolationException>()
                   .WithMessage("sample*")
}

#Error messages are readable

Having good error messages is useful to quickly understand what is going wrong.

[Fact]
public void MultipleAsserts_Fluent()
{
    var my_variable = "abc";
    my_variable.Should().HaveLength(4);
}

Fluent Assertions shows variable name when possible

[Fact]
public void Test_fluentassertions()
{
    var customer = new Customer() { Name = "John", Age = 20 };
    customer.Should().BeEquivalentTo(new Customer { Name = "Jane", Age = 30 });
}

Last but not least, thanks to the Should method you cannot swap expected and actual parameters. This can be very common with built-in libraries and this lead to confusing error messages.

#Naturally extensible

You can create your own assertion methods and make them available seemlessly. All you have to do is to create extension methods for the type you want.

public static class CustomAsserts
{
    public static void MyCustomAssert(this NumericAssertions<int> assert)
    {
        // ...
    }
}

Because built-in assertion libraries use static class, they are harder to extend.

#It can evaluate multiple assertions

Sometimes you want to evaluate multiple assertions even if the first one fails. This could be useful to better understand what's going on. Using Fluent Assertions, you can use an AssertionScope to indicate to report all failing assertions at the end of the scope.

[Fact]
public void MultipleAsserts_Fluent_Scope()
{
    var str = "abc";
    using (new AssertionScope())
    {
        str.Should().HaveLength(4);
        str.Should().Contain("d");
    }
}

#Migration from xUnit

If you are already using xUnit, you can use the XUnitToFluentAssertionsAnalyzer package to help you to migrate existing assertions to Fluent Assertions

<ItemGroup>
  <PackageReference Include="XUnitToFluentAssertionsAnalyzer" Version="1.0.2">
    <PrivateAssets>all</PrivateAssets>
    <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
  </PackageReference>
</ItemGroup>

#Additional resources

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?Buy Me A Coffee