← Back

18 Jun 2024

C# Fluent Assertions

Edit: Fluent Assertions is no longer free to use as of version 8.

Fluent Assertions is a test framework agnostic Nuget package which provides a set of extension methods that allow you to more naturally specify the expected outcome of unit tests.

Get started with dotnet add package FluentAssertions

Syntax

The general format of a Fluent Assertion is that the value being asserted on comes first, then the Should() method is called on it, followed by one of the built in assertion methods. There are many useful built in assertion methods which make it easy read and write assertions. Let’s start simple,

var result = 5;
result.Should().Be(6);

In this example we expect the value provided to the Be method to equal the variable we’re asserting on. Most of the assertion methods also have an opposite equivalent, which often begin with Not, for example result.Should().NotBe(6);

Should().Be(...) works for any value type so it can always be used to compare a variable to an expected value, but depending on the type of the variable we’re asserting on, Fluent Assertions provides some helper methods to make asserting easier.

Boolean Assertion Methods

var result = true;
result.Should().BeTrue();
result.Should().BeFalse();

Ints/Floats etc

var x = 5;
x.Should().BeNegative();
x.Should().BePositive();
x.Should().BeGreaterThan(2);
x.Should().BeGreaterThanOrEqualTo(2);
x.Should().BeLessThan(2);
x.Should().BeLessThanOrEqualTo(2);

There is also

x.Should().BeInRange(3, 4); // inclusive on lower and upper bound

and

x.Should().BeCloseTo(4, 1); // x should be anywhere from 3 to 5

Strings

var x = "foo";
x.Should().BeLowerCased();
x.Should().BeUpperCased();
x.Should().NotBeNullOrWhiteSpace();
x.Should().HaveLength(5);
x.Should().StartWith("a");
x.Should().EndWith("a");
x.Should().ContainAny("Z", "A"); // string should contain any of these expected values
x.Should().ContainAll("ABC", "BCD"); // string should contain all of these expected values

Collections

int[] x = [1, 2, 3, 4];

x.Should().BeEmpty();
x.Should().ContainSingle(); // collection has only one element

x.Should().HaveCount(2);

x.Should().HaveCountGreaterThan(1);
x.Should().HaveCountGreaterThanOrEqualTo(1);
// There is also LessThan equivalents for these ^

x.Should().HaveElementAt(0, 1); // at index 0, there should be a 1

x.Should().BeEquivalentTo([1, 2, 3, 4]); // collections compared by value, not reference

x.Should().Contain(x => x > 10); // should contain at least one element matching the predicate
x.Should().OnlyContain(e => e > 3); // all values should match the predicate
x.Should().NotContain(e => e > 5); // no values should match the predicate
x.Should().BeInAscendingOrder(e => e);

ContainSingle can sometimes catch people out when asserting on a collection of strings.

string[] res = ["abc"];
res.Should().ContainSingle("abc");

You might look at this and think it means res should have one element which has value “abc” but this is not the case. ContainsSingle only checks if there is one element in the collection. ContainSingle("abc") as used here checks there is only one element, the “abc” argument is the because parameter, which only aids in providing a more complete exception message. See about exception messages here

To check if there is a single element “abc” in the collection you could use one of

res.Should().ContainSingle().Which.Should().Be("abc");
res.Should().BeEquivalentTo(["abc"]);
res.Should().ContainSingle().And.AllBe("abc");

See more about how And and Which here

Reference Types

Classes have a couple of useful helpers, which are also available for collections as expected.

public class Thing(int a) : IThing
{
    public int Number { get; set; } = a;
}
public interface IThing {};


IThing foo = new Thing(3);
foo.Should().BeOfType(typeof(Thing));
foo.Should().BeEquivalentTo(new Thing(3)); // compares by value

And / Which

And and Which can be used to chain assertions together. And is used for multiple separate assertions, Which is used to specify a condition for the assertion, usually when dealing with collections or properties of an object. It allows you to filter the collection or object properties based on a specific condition before applying the assertion.

string actual = "ABCDEFGHI";
actual.Should().StartWith("AB").And.EndWith("HI").And.Contain("EF").And.HaveLength(9);

response.Should().ContainSingle().Which.Should()
    .Match<ErrorModel>(x =>
        x.ErrorMessage ==
        "Required parameter Id was not provided from query string.");

dictionary.Should().ContainValue(myClass).Which.SomeProperty.Should().BeGreaterThan(0);

These could all be written as separate assertions, but this syntax is more succinct. I haven’t mentioned the Match method. It allows you to assert by predicate on any type. There are many methods that haven’t been covered here, like asserting on dictionaries includes Should().ContainKey(...) and ContainValue(...)

Multiple Assertions

Normally, a test will stop when the first assertion fails (because an exception is thrown), but if you want the test to continue, and collect all failed assertions regardless of whether an earlier one failed, you can do so by creating a new AssertionScope in combination with a using statement, now any and all assertions that failed, are thrown only when code execution leaves the scope of the using statement

using (new AssertionScope())
{
    var text = "abc";
    text.Should().HaveLength(5);
    text.Should().BeUpperCased();
}
// gives exception message:
// Expected text with length 5, but found string "abc" with length 3.Expected all characters in text to be upper cased, but found "abc".

Error Messaging

By default I think the error messages provided by Fluent Assertions are better than test framework defaults because they are easier to read at a glance, for example

var sut = 5;
sut.Should().Be(6);
/* Exception message:
Expected sut to be 6, but found 5. */

// NUnit assert
Assert.That(sut, Is.EqualTo(6));
/* Exception message:
  Assert.That(sut, Is.EqualTo(6))
  Expected: 6
  But was:  5  */

All the assertions provided by Fluent Assertions come with an optional “because” parameter than can be supplied to enhance the error message.

sut.Should().Be(6, "if it is not 6, the application blows up");
/* Exception message:
Expected sut to be 6 because if it is not 6, the application blows up, but found 5.
*/