Skip to main content

Asserting exceptions

It is not uncommon for exceptions to be part of our application's logic. This means that we should be unit testing that exceptions are thrown when they should be.

But here is the question. How do we assert that an exception is thrown, when throwing an exception in a test will make it fail?

Let's say that we want to write a test called GetQuoteAsync_ShouldThrowException_WhenSameCurrencyIsUsed which ensures that an exception is thrown when the baseCurrency and the quoteCurrency are the same value.

[Fact]
public async Task GetQuoteAsync_ShouldThrowException_WhenSameCurrencyIsUsed()
{
// Arrange
var fromCurrency = "GBP";
var toCurrency = "GBP";
var amount = 100;
var expectedRate = new FxRate
{
FromCurrency = fromCurrency,
ToCurrency = toCurrency,
TimestampUtc = DateTime.UtcNow,
Rate = 1.6m
};

_ratesRepository.GetRateAsync(fromCurrency, toCurrency)
.Returns(expectedRate);

// Act
var quote = _sut.GetQuoteAsync(fromCurrency, toCurrency, amount);
}

This is as far as our test will go before it failed with a SameCurrencyException.

To solve that problem, we need to wrap our Act part in an Action. Once we do that we can use FluentAssertions' Should().ThrowAsync<ExceptionType> extension methods to validate that the right exception was thrown, without failing our test.

This will make our Act look like this:

// Act
var quoteAction = () => _sut.GetQuoteAsync(fromCurrency, toCurrency, amount);

And our Assert look like this:

// Assert
await quoteAction.Should().ThrowAsync<SameCurrencyException>();

And now our test will pass 🥳!

There is however one thing that we haven't tested and we really should. That's the exception message. We can use the chained WithMessage method to achieved that.

// Assert
await quoteAction.Should().ThrowAsync<SameCurrencyException>().WithMessage($"You cannot convert currency {fromCurrency} to itself");