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.
- These exceptions are more often can be categorized in terms of Technical vs Business exception
- 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,
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.
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 ?
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)
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!” ?
Thanks Leszek . Glad you liked the article. I have update the article for the inputs you mentioned.. Thank you!