Diagnosing Failing Tests More Easily and Improving Test Code Readability

Sometimes the assertions that come bundled with a testing framework are suboptimal in that they do not provide test failure messages that allow easier understanding of why/where the test failed.

If the test failure message does not provide enough information, it may be necessary to run the test in debug mode just to find out what went wrong before fixing it. This test debugging step is wasted time.

Using the built-in assertions can also be suboptimal from a code readability point of view, though this can be a matter of personal preference.

The Fluent Assertions library aims to solve these two problems by:

  • Providing better, more descriptive test failure messages; and
  • Providing a more fluent, readable  syntax for assertions

Let’s take a look at some examples. Note that Fluent Assertions is an “add on” to whatever testing framework you are using (NUnit, xUnit.net, etc.).

The following test (using NUnit) shows a simple case:

public class CreditCardApplication
{
    public string Name { get; set; }
    public int Age { get; set; }
    public decimal AnnualGrossIncome { get; set; }

    public int CalculateCreditScore()
    {
        int score = 0;

        if (Age > 30)
        {
            score += 10;
        }

        if (AnnualGrossIncome < 50_000)
        {
            score += 30;
        }
        else
        {
            score += 30;
        }

        return score;
    }
}

 

[Test]
public void NUnitExample()
{
    var application = new CreditCardApplication
    {
        Name = "Sarah",
        Age = 31,
        AnnualGrossIncome = 50_001
    };

    Assert.That(application.CalculateCreditScore(), Is.EqualTo(50));
}

When this test fails using the built-in NUnit asserts, the failure message is:

Test Outcome:    Failed

Result Message:    
Expected: 50
  But was:  40

Notice in the preceding test failure message we don’t have any context about what is failing or what the number 50 and 40 represent. While well-named tests can help with this, it can be helpful to have additional information, especially when there are multiple asserts in a single test method.

The same test in xUnit.net:

[Fact]
public void XUnitExample()
{
    var application = new CreditCardApplication
    {
        Name = "Sarah",
        Age = 31,
        AnnualGrossIncome = 50_001
    };

    Assert.Equal(50, application.CalculateCreditScore());
}

Produces the message:

Test Outcome:    Failed

Result Message:    
Assert.Equal() Failure
Expected: 50
Actual:   40

Once again there is no additional context about the failure.

The same test written using Fluent Assertions would look like the following:

[Fact]
public void XUnitExample_WithFluentAssertions()
{
    var application = new CreditCardApplication
    {
        Name = "Sarah",
        Age = 31,
        AnnualGrossIncome = 50_001
    };


    application.CalculateCreditScore().Should().Be(50);
}

Now when the test fails, the message looks like the following:

Test Outcome:    Failed
Result Message:
Expected application.CalculateCreditScore() to be 50, but found 40.

Notice the failure is telling us the method (or variable) name that is being asserted on – in this example the CalculateCreditScore method.

Optionally you can also add a “because” to further clarify failures:

[Fact]
public void XUnitExample_WithFluentAssertions_Because()
{
    var application = new CreditCardApplication
    {
        Name = "Sarah",
        Age = 31,
        AnnualGrossIncome = 50_001
    };


    application.CalculateCreditScore().Should().Be(50, because: "an age of {0} should be worth 20 points and an income of {1} should be worth 30 points.", application.Age, application.AnnualGrossIncome);
}

This would now produce the following failure message:

Test Outcome:    Failed
Result Message:    
Expected application.CalculateCreditScore() to be 50 because an age of 31 should be worth 20 points and an income of 50001 should be worth 30 points., but found 40.

Notice the “because” not only gives a richer failure message but also helps describe the test. While you probably wouldn’t use the because feature on every assert, you could use it to clarify tests that may not be obvious at first sight or that represent complex domain logic or algorithms. You should also be aware that the because text may need modifying if the business logic changes and this may introduce an additional maintenance cost.

If you want to learn more about Fluent Assertions, check out my express Pluralsight course Improving Unit Tests with Fluent Assertions which you can get access to with a Pluralsight free trial by clicking the banner below.

SHARE:

Add comment

Loading