Using FluentAssertions for C# Unit Tests

This article provides a concise guide to performing assertion validation in C# using FluentAssertions. You will learn how to set up the library, use its fluent syntax for common data types, validate complex object graphs, assert thrown exceptions, and work with collections to make your unit tests more readable and expressive.

Getting Started

To use FluentAssertions, install the NuGet package into your test project via the .NET CLI:

dotnet add package FluentAssertions

Then, include the namespace at the top of your test files:

using FluentAssertions;

FluentAssertions works seamlessly with any major testing framework, including xUnit, NUnit, and MSTest.

Basic Value Assertions

FluentAssertions replaces traditional assertion methods (like Assert.AreEqual) with natural language extension methods prefixed with .Should().

// Numbers
int count = 10;
count.Should().Be(10);
count.Should().BeGreaterThan(5);
count.Should().BeInRange(1, 20);

// Booleans
bool isCompleted = true;
isCompleted.Should().BeTrue();

// Strings
string message = "Order successfully created";
message.Should().StartWith("Order")
       .And.EndWith("created")
       .And.Contain("successfully");

Collection Assertions

Validating lists, arrays, and dictionaries is straightforward with built-in collection methods that handle order, count, and containment.

var numbers = new List<int> { 1, 2, 3, 4, 5 };

numbers.Should().NotBeEmpty()
       .And.HaveCount(5)
       .And.ContainInOrder(1, 2, 3)
       .And.OnlyHaveUniqueItems();

// Checking items against a condition
numbers.Should().AllSatisfy(x => x.Should().BeGreaterThan(0));

Exception Assertions

To verify that a specific exception is thrown, wrap the execution in an Action or Func and call .Should().Throw<T>().

Action act = () => account.Withdraw(500);

act.Should().Throw<InvalidOperationException>()
   .WithMessage("*insufficient funds*");

For asynchronous methods:

Func<Task> act = async () => await service.ProcessOrderAsync(null);

await act.Should().ThrowAsync<ArgumentNullException>()
         .WithParameterName("order");

Object Graph and Equivalency Assertions

When testing complex types, comparing individual properties manually can be tedious. FluentAssertions provides BeEquivalentTo(), which compares entire object trees property-by-property rather than by reference:

var expectedUser = new User { Id = 1, Name = "Alice" };
var actualUser = userService.GetUser(1);

actualUser.Should().BeEquivalentTo(expectedUser);

You can customize the comparison logic, such as excluding specific fields:

actualUser.Should().BeEquivalentTo(expectedUser, options => 
    options.Excluding(u => u.CreatedDate));

Multiple Assertions with AssertionScope

By default, an assertion failure halts the test immediately. Wrapping multiple assertions in an AssertionScope collects all failures and reports them together:

using (new FluentAssertions.Execution.AssertionScope())
{
    user.Name.Should().Be("Alice");
    user.Email.Should().Be("alice@example.com");
    user.IsActive.Should().BeTrue();
}