Test Expressiveness

26 Feb 2018

We have a test suite at work which tests a retry decorator class works as expected. One of the tests checks that when the inner implementation throws an exception, it will log the number of times it has failed:

[Test]
public async Task ShouldLogRetries()
{
    var mockClient = Substitute.For<IContractProvider>();
    var logger = Subsitute.For<ILogger>();
    var sut = new RetryDecorator(mockClient, logger, maxRetries: 3);

    mockClient
        .GetContractPdf(Arg.Any<string>())
        .Throws(new ContractDownloadException());

    try
    {
        await sut.GetContractPdf("foo");
    }
    catch (Exception e){}

    logger.Received(1).Information(Arg.Any<string>(), 1);
    logger.Received(1).Information(Arg.Any<string>(), 2);
    logger.Received(1).Information(Arg.Any<string>(), 3);
}

But looking at this test, I couldn’t easily work out what the behaviour of sut.GetContractPdf("foo") was supposed to be; should it throw an exception, or should it not? That fact that there is a try...catch indicates that it might throw an exception, but doesn’t give any indication that it’s required or not.

try
{
    await sut.GetContractPdf("foo");
}
catch (Exception e)
{
}

Since we have the Shouldly library in use, I changed the test to be a little more descriptive:

Should.Throw<ContractDownloadException>(() =>
    sut.GetContractPdfForAccount("foo")
);

Now we know that when the decorator exceeds the number of retries, it should throw the inner implementation’s exception.

This in itself is better, but it also raises another question: Is the test name correct? Or should this now be two separate tests? One called ShouldLogRetries, and one called ShouldThrowInnerExceptionOnRetriesExceeded?

Even though I ended up adding the second test, I still left the first test with the Should.Throw(...) block, as it is still more descriptive at a glance than the try...catch.

code, c#, testing

« Task Chaining and the Pipeline Operator Writing Conference Talks »