Cleaner tests with XUnit's IAsyncLifetime and expression-bodied members

01 August 2020 - .NET , Testing

When writing tests, sometimes it can be tempting to dump a bunch of asserts into the same test to avoid duplication across multiple tests...

public class MyTests
{
    [Fact]
    public void SomeTest()
    {
        var sut = new SystemUnderTest();

        var result = sut.DoSomething();

        result.StatusCode.Should().Be(Okay);
        result.Amount.Should().Be(123);
        result.UserName.Should().Be("Bob");
    }
}

This is a very simplistic example. Quite often you'd also have to inject mocks into the SystemUnderTest constructor, and do various other setup logic. You wouldn't want to duplicate that in each test, as your test class would quickly become huge, with lots of duplicate logic.

Fortunately, in XUnit, the constructor is called once per test, so you can put the setup logic there, and not duplicate it per test.

Note that other testing libraries have similar ways to run something once per test - eg. with nunit you can create a setup method. XUnit is my goto - so this post will focus on that.

To clarify, before I get swamped with comments - this blog post is not insisting that every test can only ever have a single assert. I think it's nice if they do - but in some scenarios is does make sense for a single test to have multiple asserts. Either way - this post is just showing a nice pattern I like to use when writing tests.

Expression-Bodied Methods for the tests

If you read my recent post about Writing More Succinct C#, you'll know that I'm a big fan of Expression Bodied Methods. If your tests are only doing a single assertion (because the 'arrange' and 'act' are in the setup) - then you can use them to make your tests quite succinct...

public class MyTests
{
    private readonly MyResult _result;

    MyTests()
    {
        var sut = new SystemUnderTest();
        _result = sut.DoSomething();
    }

    [Fact]
    public void StatusCodeShouldBeOkay() =>
        _result.StatusCode.Should().Be(Okay);

    [Fact]
    public void AmountShouldBe123() =>
        _result.Amount.Should().Be(123);

    [Fact]
    public void UserNameShouldBeBob() =>
        _result.UserName.Should().Be("Bob");
}

Whilst this is more code than the single test with multiple assertions - each test is still pretty succinct, and you get the benefit that each test is testing a single specific thing, so any test failures are very clear.

Async Setup using IAsyncLifetime

Quite often, our methods are async, and we can't make constructors async. This is where XUnit's IAsyncLifetime comes in. Making your test class implement IAsyncLifetime will force you to implement two methods: InitialiseAsync and DisposeAsync. So taking the above example, if sut.DoSomething() was actually sut.DoSomethingAsync() - then we could do this...

public class MyTests : IAsyncLifetime
{
    private MyResult _result;
    private SystemUnderTest _sut;

    MyTests()
    {
        _sut = new SystemUnderTest();
        // ...Other initialisation...
    }

    public async Task InitializeAsync() =>
        _result = await _sut.DoSomethingAsync();

    public Task DisposeAsync() => Task.CompletedTask;

    [Fact]
    public void StatusCodeShouldBeOkay() =>
        _result.StatusCode.Should().Be(Okay);

    [Fact]
    public void AmountShouldBe123() =>
        _result.Amount.Should().Be(123);

    [Fact]
    public void UserNameShouldBeBob() =>
        _result.UserName.Should().Be("Bob");
}

Summary

To give credit where credit's due - I discovered this pattern from a fellow contractor I'm currently working with - Lewis Henson.

Combining putting setup in constructor/InitializeAsync, and using expression-bodied-methods - really makes the tests nice and succinct, as you can see in the above examples.

This pattern is a nice way of organising your tests if you have a bunch of assertions based on a shared setup ('arrange' and 'act'). It doesn't apply in all circumstances - if you have a single 'arrange', 'act', 'assert' - then that could just go into the test itself. Or if you have a different setup for each, that's similar but not the same - you can either create a helper method that takes parameters to abstract out the duplication - or use parameterised tests.

Also for completeness - in case anyone's wondering and hasn't seen the Should syntax before - this uses an amazing library called Fluent Assertions. It makes writing asserts so much nicer. Definitely worth looking into if you've not seen it before! There's a lot of different extensions methods in this library which make various different types of assertion very clear and readable.

Finally, something I discovered recently from a blog post by Adam Storr - "C#8 Using Declarations With FluentAssertions"... If you do want to have multiple asserts in the same test, and you want a test failure to show all of the failing asserts in one go - then you can wrap them in Fluent Assertion's 'Assertion Scopes'...

using var scope = new AssertionScope();

result.StatusCode.Should().Be(Okay);
result.Amount.Should().Be(123);
result.UserName.Should().Be("Bob");

Happy Testing!