Creating Custom Assertions With Fluent.Assertions

FluentAssertions logo

I’ve been using Fluent.Assertions for years .NET, but until this week I had never looked into the community extensions. I started adding unit tests to a new controller I was creating in an existing ASP.NET Core MVC project, so I decided to take a look at FluentAssertions.AspNetCore.Mvc.

The primary use case I was unit testing was a controller that returned a ViewResult, but with two different status codes. On the happy path it would return a 200 OK, but if the user did not have the correct role it would return an error page with 401 Unauthorized. Unfortunately, there was no built-in assertion for the status code of a ViewResult.

Luckily, with extension methods we can easily add our own! I cloned the repo for FluentAssertions.AspNetCore.Mvc locally and took a look at how ViewResultAssertions worked. For example, here’s the WithViewName assertion. It tests that the name of the view to render matches the expected view name.

public ViewResultAssertions WithViewName(
    string expectedViewName,
    string reason = "",
    params object[] reasonArgs)
{
    var actualViewName = ViewResultSubject.ViewName;

    Execute.Assertion
        .BecauseOf(reason, reasonArgs)
        .ForCondition(string.Equals(expectedViewName, actualViewName, StringComparison.OrdinalIgnoreCase))
        .WithDefaultIdentifier("ViewResult.ViewName")
        .FailWith(FailureMessages.CommonFailMessage, expectedViewName, actualViewName);
    return this;
}

Using that as an example, it was straightforward to write an extension method to test the status code on a ViewResult. The highlighted lines show how to get the actual status code and then compare it using the FluentAssertions execution API.

public static ViewResultAssertions WithStatusCode(
    this ViewResultAssertions assertions,
    HttpStatusCode expectedStatusCode,
    string reason = "",
    params object[] reasonArgs)
{
    var viewResultSubject = (ViewResult)assertions.Subject;
    var actualStatusCode = (HttpStatusCode)viewResultSubject.StatusCode.Value;

    Execute.Assertion
        .BecauseOf(reason, reasonArgs)
        .ForCondition(expectedStatusCode == actualStatusCode)
        .WithDefaultIdentifier("ViewResult.StatusCode")
        .FailWith("Expected status code {0}, but found {1}", expectedStatusCode, actualStatusCode);
    return assertions;
}

Nothing about this assertion is specific to my project, so I should take the time to submit it as a PR. After writing this, I even found that other result types, like JsonResultAssertion, have almost this exact implementation to check status codes.

The most exciting part to me though was seeing how nice the FluentAssertions execution API is. I’ll definitely be keeping an eye open towards creating my own custom assertion extension methods for repeated test patterns as I continue writing tests.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s