Unit Testing Exceptions – Best Practices

In this article, we will see how to write Unit testing Exceptions scenarios in the .NET/.NET Core ecosystem using C#.

We shall be using XUnit as a Unit Testing framework to understand this for today’s article however below guidelines are generic enough to be applied to NUnit or MSTest or any other Test framework

Guidelines For Exception Unit Testing

  • Exceptions are an integral part of your application.
  • Write Unit Tests for each possible +ve, -ve scenario including exception.
  • UnitTest should verify thrown exceptions for Technical exceptions.
  • UnitTest should verify thrown exceptions for a Business exception.
  • The assertion should be done for the Type of Exception. Few business exceptions might be critical enough to be unit tested for their types.
  • The assertion should be done for the Exception message details if required. Few business exceptions messages details might be critical enough to be unit tested for internal messages.
  • Unit test cases for exceptions should improve the stability and robustness of your application.
  • Unit Test cases can ensure of proper exception handling is implemented.
  • Unit Test cases can ensure the exception message is not exposing any sensitive details.
  • Do not use Assert.Throws() to check for Asynchronously thrown exceptions. (applicable for XUnit, NUnit or MSTest).
  • You must use ThrowsAsync<T> for an async operation.
  • Mark your Unit test method as Async if performing AsyncException handling.
  • Always check for exception messages that are thrown are correct and as per the requirements.

You will get that Exception ??

If you are confident enough to say an exception will occur for certain scenarios then you must code for it and hence you must unit test case those.

Unit Testing Exception Thrown by Synchronous Methods

Let’s take a below simple example,

Method Under Test

We shall be using the below synchronous method,

  public string GetAccountDetails(string queryString)
        {
            if (string.IsNullOrEmpty(queryString))
            {
                throw new ArgumentNullException();
            }
            return "Success";
        }

Below is the Unit Test sample using the ‘AAA’ pattern, For more information AAA pattern , please visit Unit Test Naming Conventions Best Practices

        [Fact]
        public async Task BookService_GetBook_With_Exception()
        {

            //Arrange
            EmployeeLoanDetails details = new EmployeeLoanDetails();

            //Act
            Action act = () => details.GetAccountDetails("");
            
            // Assert
            Assert.Throws<NotImplementedException>(act);
        }

As shown above we can verify the type of exception using the statement Assert.Throws<T>

Additionally, you can use lambda or method as a delegate to invoke Throws<T>

Example

Using Lambda expression

Assert.Throws<ArgumentNullException>(()=> { details.GetAccountDetails("9981"); });

Using delegate,

Assert.Throws<ArgumentNullException>(delegate { details.GetAccountDetails("9981"); });

Unit Testing Exception Thrown by Asynchronous Methods

We shall be using the below Asynchronous method,

Method Under Test

     

public async Task<IEnumerable> GetAccountDetailsAsync(string queryString)
        {
            if (string.IsNullOrEmpty(queryString))
            {
                throw new ArgumentNullException();
            }
            else
            {
                return new List<string>();
            }
        }

Below is the Unit Test sample with the ‘AAA‘ pattern,

        [Fact]
        public async Task BookService_GetBook_With_ExceptionAsync()
        {

            //Arrange
            EmployeeLoanDetails details = new EmployeeLoanDetails();

            //Act
            async Task act() => await details.GetAccountDetailsAsync("");

            // Assert
            await Assert.ThrowsAsync<ArgumentNullException>(act);
        }

Verify Exception Message is thrown properly

It’s always best practice to verify exception messages, application throwing is meaningful, and conveys the right information.

Example:

public async Task<IEnumerable> GetAccountDetailsAsync(string queryString)
        {
            if (string.IsNullOrEmpty(queryString))
            {
                throw new ArgumentNullException($"Input Argument 'queryString' is NULL");
            }
            else
            {
                return new List<string>();
            }
        }

Below is the Unit Test sample with the ‘AAA’ pattern,

        [Fact]
        public async Task BookService_GetBook_With_ExceptionAsync()
        {

            //Arrange
            EmployeeLoanDetails details = new EmployeeLoanDetails();

            //Act
            async Task act() => await 
            details.GetAccountDetailsAsync("");

            // Assert
            var ex= await Assert.ThrowsAsync<ArgumentNullException>(act);

            Assert.Contains("Input Argument 'queryString' is NULL", ex.Message);

        }

Above we are making sure exception messages are matching with the actual messages.

Finally, our test methods are passing successfully,

Unit testing Exceptions

Writing unit test cases for exceptions will give you enough confidence for the code you are building.

In the runtime, your code will be prepared and robust enough to handle these permutations and combinations of exceptions, etc.

It’s always important to validate what exceptions are thrown to the end-user or consumers.

By means of Unit Testing, one can keep control of such custom exception handling and their behavior associated.

Useful references,

Do you have any comments or ideas or any better suggestions to share?

Please sound off your comments below.

Happy Coding !!

Summary

Exceptions are an integral part of the application. We understood that Unit test cases should be written for validated Technical or Business exceptions. Unit test cases for exceptions should improve the stability and robustness of your application.



Please bookmark this page and share it with your friends. Please Subscribe to the blog to receive notifications on freshly published(2024) best practices and guidelines for software design and development.



4 thoughts on “Unit Testing Exceptions – Best Practices

  1. On the line

    // Assert
    await Assert.ThrowsAsync(act);

    I get this error.

    ArgumentNullException does not contain a definition for GetAwaiter (using clause missing ?)

    I’m on VS2022 on a .Net Core application with framework .Net 6.0

    Any clues ?

    1. Hi Yves- Thanks for your query. Please make sure your target method is an async method and returns asynchronous responses.Example async Task GetAccountDetailsAsync(string queryString)

  2. Thanks for the comparison sync vs async.
    One thing is missing here – how do you test that exception, like ex.Message = “this was my exception!” ?

    1. Thanks Leszek . Glad you liked the article. I have update the article for the inputs you mentioned.. Thank you!

Leave a Reply

Your email address will not be published. Required fields are marked *