Background

FluentAssertions is a widely used assertion framework for .NET that provides all the fluidy syntax goodness in the assertions space, allowing for clear and concise assertions. Its primary benefit is that it improves the readability (and ultimately the maintainability) of your tests. In this post, we’ll look at the different methods supported by FluentAssertions.

Intro to FluentAssertions

Traditional assertion libraries often result in more complex and harder-to-read code.

For example…

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
    Assert.AreEqual(42, result);
    
    Assert.Contains(myList, item => item.Id == expectedId);
    
    try
    {
        myObject.DoSomethingThatThrows();
        Assert.Fail("Expected exception not thrown");
    }
    catch (ExpectedException ex)
    {
        Assert.AreEqual("Expected message", ex.Message);
    }
    
    Assert.IsNotNull(myObject);
    

FluentAssertions tackles this problem by offering a more natural, fluid, and intuitive syntax for writing assertions. It has a wide and comprehensive API that covers a wide variety of use-cases, supporting everything from simple equality checks to complex object graph comparisons and exception assertions.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    // Assert.AreEqual(42, result);
    result.Should().Be(42);
    
    // Assert.Contains(myList, item => item.Id == expectedId);
    myList.Should().Contain(item => item.Id == expectedId);
    
    // See lots of lines in example above for the exception testing     
    Action act = () => objectUnderTest.MethodThatShouldThrow();
    act.Should().Throw<ExpectedExceptionType>();
    
    // Assert.IsNotNull(myObject);
    myObject.Should().NotBeNull();

Key Features of FluentAssertions

  1. Readability - The assertions read like an English sentence.

  2. Detailed Error Messages - FluentAssertions generate descriptive error messages out of the box.

Expected result to be "TessTickles" with a length of 11, but "Tess Tickles" has a length of 12, differs near " Ti" (index 4).

  1. Variety of Assertion Types - FluentAssertions supports assertions on various types, such as numbers, strings, collections, and more complex types. You can assert equality, order, type, size, and many other properties in a very natural way.

  2. Exception Assertions - FluentAssertions makes asserting exceptions thrown by your code easier and more intuitive:

To test that an exception is thrown, assign the method to an action and then assert the expected exception and optionally also the message that is to be returned.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
[Fact]
public void Should_ThrowException_WhenDivisorIsZero()
{
    var x = 0;
    Action result = () => { var res = 1 / x; };
    
    result.Should()
        .Throw<DivideByZeroException>()
        .WithMessage("Attempted to divide by zero.");
}

  1. Object Graph Comparison - You can compare entire object graphs for equivalency, treating properties of custom types as value types by default. If an object doesn’t override the Equals method and ordinarily it would be performing reference equality then FluentAssertion will use reflection and compare all of the properties. This saves you having to manually test each individual property.

This is useful when comparing complex objects that may have nested properties or properties that are not directly comparable using standard equality checks.

1
2
3
    var actual = new { Name = "John", Age = 30 };
    var expected = new { Name = "John", Age = 30 };
    actual.Should().BeEquivalentTo(expected);

Another good example of this…

1
2
3
    var expected = new List<int> { 1, 2, 3 }; 
    var actual = new List<int> { 3, 2, 1 }; 
    actual.Should().BeEquivalentTo(expected);

In this example, the BeEquivalentTo method is used to compare two lists of integers. Even though the lists have a different order of elements, the BeEquivalentTo method will still consider them equivalent since they contain the same values.

The BeEquivalentTo method supports several options for customising the comparison, such as ignoring specific properties, specifying how to compare nested collections, and more.

An example of ignoring a property…

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
    var person = new Person
    {
        Name = "John",
        Age = 30,
        Address = "123 Some Street"
    };
    
    var anotherPerson = new Person
    {
        Name = "John",
        Age = 30,
        Address = "New York"
    };
    
    person.Should().BeEquivalentTo(anotherPerson, options => options.Excluding(p => p.Address));

  1. Testing Events

You Can also test that events are triggered by monitoring the target class. Very neat!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public class EventCallbackExample
{
    public event EventHandler<int> Something;

    public int DoSomething(int x, int y)
    {
        var result = x + y;
        Something?.Invoke(this, result);
        return result;
    }
}  

[Fact]
public void TestEventIsRaised()
{
    var callbackExample = new EventCallbackExample();
    var monitorSubject = callbackExample.Monitor();
    callbackExample.DoSomething(1, 2);

    monitorSubject.Should().Raise("Something");
}