Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / DevOps / unit-testing

How to do Multiple Assertions with Fluent Assertions?

5.00/5 (2 votes)
14 Aug 2023CPOL2 min read 8.6K  
Explains how to do multiple assertions using the Fluent Assertions library, without multiple validation lines
Doing multiple assertions using Fluent Assertions is often done by simply adding multiple assertion lines to the assertion section of a unit test. This makes maintenance more difficult than it is supposed to be as it can be done in one line in most cases.

Introduction

A lot of code returns a complex object. Just like other code, it needs to be unit tested. This is logically done by just adding multiple assertion lines to the test. This sounds logical, but is a problem for maintenance. Each added line needs to be maintained. Moreover, each line can cause a separate failure.

In this article, I'll explain the problem in more detail and I'll demonstrate how to deal with this problem.

Background

It is essential to have some experience with unit testing in .NET, preferably with xUnit and Fluent Assertions.

Using the Code

Here is the code to be unit tested. It is a class that holds properties about the health of a human being. As the BMI cannot be measured, it is calculated.

C#
public class HealthDescription
{
    public decimal LengthInM { get; }
    public decimal WeightInKg { get; }
    public decimal Bmi { get;  }
    public HealthDescription(decimal weightInKg, int lengthInCm)
    {
        WeightInKg = weightInKg;
        LengthInM = (decimal)lengthInCm / 100;
        Bmi = Math.Round(weightInKg / (LengthInM * LengthInM), 2);
    }
}

The first unit test to demonstrate is this one:

C#
[Theory]
[InlineData(70.01, 180, 1.80, 21.61)]
public void RegularTest(decimal weightInKg, int lengthInCm, 
       decimal expectedLengthInM, decimal expectedBmi)
{
    var instance = new HealthDescription(weightInKg, lengthInCm);
    instance.LengthInM.Should().Be(expectedLengthInM);
    instance.Bmi.Should().Be(expectedBmi);
    instance.WeightInKg.Should().Be(weightInKg);
}

This test shown above has two problems:

  1. There are multiple lines to maintain.
  2. Not all assertions are executed if the first or second assertion fails.

The last problem is easy to fix. Here is a way to do that:

C#
[Theory]
[InlineData(70.01, 180, 1.80, 21.61)]
public void ScopeTest(decimal weightInKg, int lengthInCm, 
       decimal expectedLengthInM, decimal expectedBmi)
{
    var instance = new HealthDescription(weightInKg, lengthInCm);
    using (new AssertionScope())
    {
        instance.LengthInM.Should().Be(expectedLengthInM);
        instance.Bmi.Should().Be(expectedBmi);
        instance.WeightInKg.Should().Be(weightInKg);
    }
}

We simply wrap the multiple lines into one assertion scope in order to ensure all validations are executed (as these are part of that same scope). However, the first problem mentioned is the number of lines. This has just been made even worse.

Therefore, we need to look further for real solutions solving both problems. Here is one solution: using value tuples.

C#
[Theory]
[InlineData(70.01, 180, 1.80, 21.61)]
public void ValueTupleTest(decimal weightInKg, int lengthInCm, 
                           decimal expectedLengthInM, decimal expectedBmi)
{
    var instance = new HealthDescription(weightInKg, lengthInCm);
    (instance.LengthInM, instance.Bmi, instance.WeightInKg).Should()
        .Be((expectedLengthInM, expectedBmi, weightInKg));
}

The idea behind this code is to wrap the properties to validate into one object. Then, just one validation (line) is needed. In addition, there is one extra solution by combining FluentAssertions.Json with literal strings. In this way, json objects with the properties to validate, are validated as one object.

C#
[Theory]
[InlineData(70.01, 180, """
                        {
                        "Bmi" : 21.61,
                        "WeightInKg": 70.01,
                        "LengthInM" : 1.80 
                        }
                        """)]
public void RegularJsonTest(decimal weightInKg, int lengthInCm, string expectedResult)
{
    var instance = new HealthDescription(weightInKg, lengthInCm);
    JToken.FromObject(instance).Should().BeEquivalentTo(JToken.Parse(expectedResult));
}

Points of Interest

While investigating the possibilities on how to do multiple assertions, I discovered that a lot of people keep on using the ways they have been using for years. As those ways still work, they keep on using it. However, recently added C# features create new possibilities to solve the same problem in a more efficient way. The source code used in this article can be found here.

History

  • 14th August, 2023: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)