Testing Exceptions with xUnit

by Kenji Elzerman
Testing Exceptions with xUnit - Kens Learning Curve
Things you should know

For this tutorial you need to know or have the following terms/techniques:

In a previous tutorial, I explained the basics of unit testing with xUnit and C#. That tutorial is based on a happy flow and best-case scenarios. I talked about the Fact and Theory attributes. The Test Explorer has been explained too.

But in some cases, your code might throw an exception, which causes the application to stop working and close it. A unit test is somewhat of a small application. So, how do we fix this? How can we handle exceptions? Better yet; how can are we testing exceptions with xunit?

Preparations

Before we start, it’s best to have an application that throws exceptions. I used the code from the previous tutorial and changed a few things. You can find the code here:

https://github.com/KensLearningCurve/ExceptionHandlingWithxUnit

A few things to mention:

  1. MyMovies.cs is the class that has all the logic. We will create tests for this class.
  2. The method Get(int id) throws an exception when the ID is zero or less.
  3. The method Delete(int id) throws a MovieNotFoundException(), which is a custom exception and can be found in the folder Exceptions.

The methods that throw exceptions are known exceptions. This means we know these will be thrown if someone uses them wrong. We should test if these exceptions are thrown in particular cases (ID zero of less and movie not found when trying to delete something).
Known exceptions are exceptions you throw to make the called (front-end / user) aware they did something wrong.

If your code throws a not known exception it means your code has a serious error. If you don’t expect an exception you should fix it and not test it.

A last note about the code: Although these can not be very good exceptions not the locations they are thrown, these exceptions are here to give you some idea of how to test them. It’s for demonstration purposes only.

Addressing the problem

Since I have created a whole tutorial about xUnit and how to start, I just tell you I add a new xUnit project to my solution, called “ExampleClass.Tests”, and I add a new test class with the name “MyMoviesTests”. Within that class, I add two unit tests for Get(int id). These unit tests will pass without a problem.

Okay, cool! Two unit tests that work! But these two unit tests are not covering the whole method Get(int id). For the whole coverage we need to test the if-statement and exception too.

We can make a unit test like this:

Yes, this looks good. But the unit test will fail all the time. Why? Because the code of Get(int id) throws an exception. Yes, but we want that, so it should succeed. Correct, but that is not how it works.

Exception with a unit test - Testing Exceptions with xUnit - Kens Learning Curve
The exception we expect, but the test fails.

We need to catch the exception in a secluded piece of code, execute that code when we want, catch the exception, and check it with an assertion.

How not to do it

Some people will think: “Ow, but let’s surround the myMovies.Get(id) with a try-catch!” Yes, that could work. The code would look something like this:

To me, personally, it looks a bit weird. Besides, try-catch blocks are made to treat exceptions during execution time, not for testing. Of course, these blocks do work, but there is a protocol. Testing exceptions with xUnit gives us extra tools to test these exceptions. Assert has build-in methods to test exceptions. Use these, not a try-catch block.

And action!

But how do we test exceptions? I gave a hint earlier:

We need to catch the exception in a secluded piece of code, execute that code when we want, catch the exception, and check it with an assertion.

With that said… How do we do that? Using an action!

An action is a delegate that can encapsulate a method that zero or more parameters and doesn’t return a value. It’s perfect for storing a method and executing it later.
The question I regularly get is: “Why not use a func?” The answer is simple: A func is the same but is used for methods that return a value. Since we expect an exception (which isn’t a return value), the method does not send anything back… Ever.

Assert has build-in methods to execute an action a catch the exception. From there you can take over and validate the exception and its contents.

Look at the following code:

What I have done is put the myMovies.Get(id) into action and use Assert.Throws<ArgumentException>(act) to execute the act. The Throws is generic and it expects the type of exception that is being thrown. This is your first assertion for this test because there is more to it.

What if a method throws several ArgumentExceptions with different messages? Your unit test could succeed, but because of the wrong exception. For that reason it is a valid point to examine the message of the exception. But the exception is caught by the Action. So how do we get it?

If you look carefully at the Throws-method (hover your mouse over it) you see this method returns the exception as a return value. Which means you can use a variable to get the contents of the exception. It looks like this:

This way you can first check if the correct exception is thrown and after that, you check if the message of the thrown exception is correct.

If you look at it it looks pretty easy, if you know it.

Conclusion

Exceptions are a vital part of our applications (web, desktop, or mobile). They tell the front-end something is wrong. Most exceptions are known. Meaning they are thrown by the developer to send a message. These known exceptions are part of the logic and therefore testable. Testing exceptions with xunit isn’t that hard, you just need to know how.
When a unit test throws an exception you don’t expect it’s a problem. You don’t test for it, you fix it. Unit tests are here to help you discover these unknown exceptions and fix them before your users use the application.

If you use Action to encapsulate the method that causes the exception, you can capture it and validate it with assertions. Testing for exceptions using try-catch blocks is not done. It works, but it’s not how we do it.

 

Leave a Comment

* By using this form you agree with the storage and handling of your data by this website.

Table Of Contents
Kens Learning Curve