C# Fluent Assertions for Unit Testing

Jun 25, 2023 | C#, .NET

Unit testing is a key part of any productive software development process. Using the right tools not only makes the process easier but also more effective. This is where Fluent Assertions, a set of extension methods for .NET, comes in. We’re going on a deep dive through this flexible library to maximize your testing skills. Ready?

An Introduction to Fluent Assertions in C#

Let’s start at the beginning. Fluent Assertions is a .NET framework designed to make your unit test assertions more readable, maintainable and, most importantly, fluent! So, why should you care? Well, hold on, we’re getting there in just a second.

Understanding Fluent Assertions

The main beauty of Fluent Assertions lies in its readability. Its syntax is designed to read like English sentences, making it easy for anyone to understand your test cases. As a C# developer, your code is your craft, and Fluent Assertions makes the kind of code that’s a pleasure to read and write. Don’t believe me? Check this out.

//Non-fluent assertion
Assert.AreEqual(3, myList.Count);
//Fluent assertion
myList.Should().HaveCount(3);

You see, which one reads more like a sentence you’d say in a conversation?

The Importance of Fluent Assertions in C#

I get it, you might be asking, “I have been doing unit testing without Fluent Assertions, why should I bother now?”. Well, fluent assertions simplify unit testing process by removing unnecessary complexity. You get clearer, more maintainable code. That’s something we all want, right?

Installation and Setup of Fluent Assertions

Before we dive deeper, let’s make sure you have Fluent Assertions up and running. No worries, it’s as easy as pie!

How to Add Fluent Assertions to a C# Project

To add Fluent Assertions to your project, you can use the NuGet package manager. Here’s a quick example using the Package Manager Console.

PM> Install-Package FluentAssertions

That’s it! Fluent Assertions is now ready for use in your project. Piece of cake, huh?

Setting up Fluent Assertions

The next step is to add a using directive to your test file. This will allow you to start using the Fluent Assertions methods in your tests.

using FluentAssertions;

Once this is done, you’re all set to start using Fluent Assertions in your unit tests.

The Core Concepts of Fluent Assertions

Now that you have Fluent Assertions installed, let’s talk a bit about the core concepts. Within this framework, there are several important concepts that will give you that “Aha!” moment once you understand them.

The Main Assertions Methods

Most of the assertions methods are provided by the Should() method which is added by Fluent Assertions to a variety of types. It means that you can use it on almost any type you’re working with, which is super handy!

Here’s a quick glance at how it looks:

myNumber.Should().Be(42);

Can you guess what this is doing? Exactly, it’s verifying that myNumber is 42!

Chaining Assertions – The ‘And’ Concept

Remember when I said Fluent Assertions wants to make your assertions look like English sentences? Well, the ‘And’ concept is another beautiful illustration of this. Here, you make a statement, say “And”, and continue with another assertion. Easy!

myObj.Should().NotBeNull().And.BeOfType<MyClass>();

This line tells myObj should not be null and it should be of type MyClass.

Exception Testing with Fluent Assertions

Are you used to try/catch blocks in your tests to verify exception behaviours? Fluent Assertions provides a more elegant way to do it using Invoking and Should().Throw. The nerd in me finds it quite splendid!

Action act = () => myObj.SomeDangerousMethod();
act.Should().Throw<SomeException>();

Say bye to the messiness of try/catch and hello to cleaner, focused test code!

Using Assertion Scope

Assertion Scope is another neat feature. It allows you to batch assertions together and have all failing ones reported at once. It’s like sending all your tests on a mission together instead of one by one. See it in action:

using(new AssertionScope())
{
 myNumber.Should().Be(42);
 myString.Should().Be("Hello, world!");
}

All the assertions in the scope run together, so you’ll get a report on all failing ones at once. Handy, right?

Fluent Assertions

Alright! You’ve got the basics. Now let’s look at some advanced features of Fluent Assertions and how they can make your unit tests even better. Buckle up!

String and Regular Expressions Assertions

In Fluent Assertions, you can easily test strings. This includes regular expressions. It’s just as straightforward as the other assertions.

myString.Should().StartWith("Hello").And.EndWith("world!");
myString.Should().MatchRegex(pattern); //Ensure myString matches the given regular expression pattern

These are just a few of the countless string assertions that Fluent Assertions supports. Pretty cool, right?

Object Graph Comparison with BeEquivalentTo

The BeEquivalentTo method is your magic wand for comparing object graphs. It recursively compares all the properties of the objects, giving you a deep comparison.

var actualPerson = new Person { Name = "John", Age = 30 };
var expectedPerson = new Person { Name = "John", Age = 30 };
actualPerson.Should().BeEquivalentTo(expectedPerson);

See? It’s like telling the two objects to look in the mirror and ask if they see themselves!

I hope you’re as excited as I am to harness the power of Fluent Assertions. Now go ahead, make your unit tests more human, readable and fluent!

Examples of Fluent Assertions

Making your unit tests happy is now a thing! We’ve covered some fundamental bits about Fluent Assertions. But, how about we see this stuff in action? Let’s look at some real-world examples.

Real-Life Use Cases of Fluent Assertions

For instance, when testing a method that populates a list of movies, you can use Fluent Assertions to confirm that the right number of movies are returned.

var movies = movieService.GetAllMovies();
movies.Should().NotBeNullOrEmpty().And.HaveCount(50); //Here we expect 50 movies

In another instance, you might want to test an API’s response. Fluent Assertions can do that pretty fluently (pun intended!).

// Testing that API response is 200 and contains 'success' message
var httpResponse = await client.GetAsync("http://api.example.com/movies");
httpResponse.StatusCode.Should().Be(200);
httpResponse.Content.ReadAsStringAsync().Should().Contain("success");

Against the old ways of unit testing in C#, Fluent Assertions feels like a refreshing change. Wouldn’t you agree?

Code Samples of Fluent Assertions

The splendid thing about Fluent Assertions is it comes with assertion helpers for most types you’re likely to work with. For instance, here’s how you can test dictating a DateTime value.

myDate.Should().BeAfter(new DateTime(2022, 01, 01)).And.BeBefore(new DateTime(2022, 12, 31));

Here, Fluent Assertions ensures myDate happens within 2022. That’s fluent to perfection!

Creating Custom Assertions in Fluent Assertions

You’ve seen the beauty of Fluent Assertions and how it can make your test cases much more readable. Now, let’s take this a notch higher by exploring custom assertions.

The Steps to Writing a Custom Assertion

Let’s say you often need to test that an object is not null and is of a specific type. Here’s how you can create a custom assertion to do just that:

public static class CustomAssertions
{
    public static void BeOfTypeAndNotNull<T>(this object actual)
    {
        actual.Should().NotBeNull().And.BeOfType<T>();
    }
}

Neat, right? Now, you can use BeOfTypeAndNotNull anywhere in your test code.

Examples of Custom Assertions

With the custom assertion we created earlier, here’s how to use it:

myObj.BeOfTypeAndNotNull<MyClass>();

Saves you a couple of keystrokes and makes your tests even more expressive. Just like that, your testing just went custom and fluent!

Alternatives to Fluent Assertions

Fluent Assertions is amazing. But hey, in the spirit of impartiality, let’s check out other cool frameworks you might want to consider.

Explore Other Testing Libraries for C#

C# has several testing libraries which include NUnit, xUnit, MSTest to name but a few. They all provide excellent functionality for unit testing with a different way of doing things.

Comparison Between Fluent Assertions and its Alternatives

It’s important to note that Fluent Assertions is not a testing framework. It exists to make your unit tests more human-readable – the kind that makes you excited to show to a non-programmer friend! Connection with your code is a great thing, isn’t it?

Deciding Whether to Use Fluent Assertions

Let’s address the elephant in the room – should you use Fluent Assertions? The answer boils down to your personal preference and the specific needs of your project. However, one fact remains irrefutable: Fluent Assertions brings to the table readability, maintainability, and, well, the undeniable cool factor in test assertions.

Evaluating Project Requirements for Fluent Assertions

If your project involves a large codebase and a team of engineers with varied programming acumen, Fluent Assertions can prove to be really beneficial. Its syntax is clean, straightforward, and natural to the human eye – much like reading a simple English sentence but with code! Understanding unit tests, especially during code reviews or debugging, becomes considerably breezier.

Consider the following example:

var area = rectangle.GetArea();

// Traditional assertion
Assert.AreEqual(20, area);

// Fluent assertion
area.Should().Be(20);

Tell me, which one seems more human-readable to you? Bets are on the latter.

But let’s take it a step further. Imagining the area calculation had some bug and it returned 15 instead of 20. The traditional assertion would throw an exception

Assert.AreEqual(20, area);  //Fail: Expected <20>, But was <15>

Compare it to Fluent Assertions:

area.Should().Be(20);  //Fail: Expected area to be 20, but found 15.

Here, the Fluent Assertion’s failure message seems more clear and detailed which would be instantly helpful for anyone reading this test failure.

The Pros and Cons of Using Fluent Assertions

Like every great superhero, Fluent Assertions also has its fair share of kryptonite. It’s a sharp knife – and while it can produce beautiful results, it also has the potential to cut the wielder if not used wisely.

  • Pros: As we’ve already pointed out, Fluent Assertions bids goodbye to cryptic assertions and introduces a syntax that’s all about fluency and simplicity. It turns your assertions into ready-to-read English sentences. Plus, you have the flexibility to create custom assertions, a superpower that regular assertion libraries often lack.
  • Cons: Starting with Fluent Assertions might bring along a slight learning curve, especially if you have been accustomed to the built-in assertion syntax. Moreover, overusing or abusing its fluency can lead to code that is technically fancy, but a bit overwhelming to read.

For instance, you might get carried away and end up chaining too many assertions like:

myObject.Should().NotBeNull().And.BeOfType<MyClass>().And.Match(o => o.Property1 > 0);

Even for the simplest Simon, this starts to strain the eyes a little, right? So, remember to keep it balanced!

Fluent Assertions is a tool designed to make your unit tests more maintainable and enjoyable to write and read. But whether you choose to wield this tool is ultimately up to you. The core idea is to create unit tests that not only validate your code’s correctness but also clearly explain what your code is supposed to do. Whether you are leaning towards utilising Fluent Assertions or still on the fence, one thing is certain – your adventure into making your tests more “fluent” is going to lead to some pretty enjoyable coding. Enjoy the ride!

Best Practices and Tips for Fluent Assertions

Becoming proficient with Fluent Assertions requires not only mastering its methods but also knowing how best to use them. Let’s explore some tips and best practices to help you make the most of Fluent Assertions. We’re aiming for readable, efficient, and effective unit tests here!

How to Save Testing Time with Fluent Assertions

The charm of Fluent Assertions lies not only in its readability but also in its potential to save significant testing time. Let’s navigate this landscape together.

One of the crucial things to remember when starting off with Fluent Assertions is to master the basics first. Familiarize yourself with the Should(), Be(), Have(), and other foundational assertion methods. Here’s an example:

var itemCount = 5;
itemCount.Should().Be(5); // Asserting that itemCount is indeed 5

This is clear, simple, and reads almost like an English sentence! And that’s what Fluent Assertions is all about. Once you’re comfortable with the above, aim for more complex methods like BeEquivalentTo(), which allows you to compare two object graphs:

var actualPerson = new Person { Name = "John", Age = 30 };
var expectedPerson = new Person { Name = "John", Age = 30 };
actualPerson.Should().BeEquivalentTo(expectedPerson); // Check if actualPerson is equivalent to expectedPerson

Using BeEquivalentTo() can save you from writing multiple assertions for each property. How efficient is that?

Next, embrace AssertionScope for multiple assertions.

var myList = new List<int>{1, 2, 3};

using (new AssertionScope())
{
    myList.Should().HaveCount(3); // It should have 3 items
    myList.Should().ContainInOrder(new List<int> { 1, 2, 3 }); // Items should be in a specific order
}

Here, all assertion failures will be aggregated and reported at once. This approach can save you plenty of time debugging your test failures.

Maximizing Your Usage of Fluent Assertions

Fluent Assertions draws its power from fluency: it aims to make tests more maintainable, expressive, and easier to understand. When trying to maximize usage, it’s crucial to factor in the readability and simplicity of your assertions.

However, if you find yourself writing complex assertions, it’s often a signal – a code smell – that your underlying code might be too complex. Ask yourself: is there a way to simplify this? If your test assertions start feeling complicated, it might be time to refactor your code.

// Let's say you have a method CalculateSum that should return 15 if it works correctly.

var result = CalculateSum(5, 4, 6);

result.Should().Be(15, because: "5 + 4 + 6 is equal to 15"); // Asserting the output of CalculateSum

By using the because parameter, not only do you make your code more readable, but you also self-document your code. If a junior developer or even an 8-year-old were to read this, they wouldn’t have a hard time understanding your intent. After all, making your tests speak the same language as your team is what Fluent Assertions is all about.

Conclusion

What a fantastic journey we’ve had into the world of Fluent Assertions! When it comes to making unit testing in C# a more enjoyable experience, Fluent Assertions stands tall. Its thoughtful design allows your tests to be more expressive and readable.

You May Also Like

Sign up For Our Newsletter

Weekly .NET Capsules: Short reads for busy devs.

  • NLatest .NET tips and tricks
  • NQuick 5-minute reads
  • NPractical code snippets
.