Effective Unit Testing for .NET Web APIs Using xUnit Framework
Written on
In this article, we will explore the fundamental principles of unit testing and demonstrate how to apply unit testing in a .NET 8 web API project utilizing xUnit. _Note: This article does not delve into the implementations of .NET Web API._
What is Unit Testing?
Unit testing refers to a type of white box testing that scrutinizes individual software components (units of work). These units may be represented by class methods in Object-Oriented Programming or functions in procedural programming. Unit tests focus on verifying the functionality of specific methods/functions without addressing infrastructure concerns such as database interactions or file systems.
Why Perform Unit Testing?
It's crucial to verify that our individual code units operate according to specifications before integration takes place. In the context of Test Driven Development (TDD), unit tests are written for expected methods/functions prior to code implementation. Therefore, unit testing is a vital aspect of software development.
Testing Frameworks for C#
There are four testing frameworks available, and developers are free to choose any of them. This article will focus on the xUnit framework for executing unit tests. The available frameworks include:
- xUnit
- NUnit
- MSTest
- MSTest Runner
xUnit
xUnit is the most recent technology for unit testing in .NET applications and is an open-source testing framework. The 'x' in xUnit represents various programming languages, including C#, F#, etc.
NUnit
NUnit serves as a unit testing framework for all .NET languages. Originally derived from JUnit, the latest version offers numerous new features and supports a broad range of .NET platforms.
MSTest
MSTest is Microsoft's test framework designed for all .NET languages. It can be utilized with both .NET CLI and Visual Studio.
MSTest Runner
This is a lightweight and portable alternative to the VSTest framework, suitable for running tests in continuous integration pipelines and Visual Studio Test Explorer.
For a deeper insight into the pros and cons of these frameworks, click here. I have chosen xUnit due to its simplicity, extensibility, and incorporation of modern testing practices.
Now, let's dive into the implementation!
Scenario
We will set up a web API that manages users within a system. This API will support operations such as creating a user, retrieving a user by ID, fetching all users, and counting the total number of users. The following figures illustrate the implemented endpoints and the service interface for the user business logic.
Let’s proceed to write unit tests for the above methods.
Step 1
Begin by adding a new xUnit Test Project to your web API project. Right-click on the solution, select Add, and then click on New Project.
Choose the xUnit Test Project and configure it as shown in the following figures.
Step 2
Add the web API project as a dependency for the newly created xUnit test project. Right-click on the unit test project, select Add, and then choose Project Reference.
Select your web API project and click OK.
Confirm your project update by checking the dependencies of the unit test project.
Step 3
The xUnit Test project comes with a sample test file. You can rename it according to standard naming conventions. Best practices for unit testing in .NET can be found here.
I renamed the UnitTest1.cs file to UserControllerTest as I will be testing the web API endpoint handler methods defined in the UserController.
Step 4
To implement unit tests for methods defined in the service class (UserService), which access the database, we need to return mock data instead of querying the database. Thus, we will need to install a NuGet package in our unit test project. For this application, I utilized the Moq NuGet package.
Step 5
Now we can create our unit tests. Let's start by writing a unit test for the ListUsers() method, as shown in the following figure.
Below is the implementation of the unit test for the ListUsers() method.
Code Breakdown - Line 12: A variable to hold an IUserService as a Mock is defined. - Lines 14-17: The test class constructor initializes the IUserService Mock variable. - Lines 19-67: The unit test method is defined. The Fact attribute indicates that the TestGetUsers() method is a unit test method for the xUnit framework. xUnit has two primary categories of unit test method attributes: Fact (for parameter-less tests) and Theory (for tests that require input values).
The AAA (Arrange, Act, Assert) pattern is often used in C# to create well-structured unit tests: 1. Arrange: Initialize variables and set values for the data to be passed to the method being tested. 2. Act: Invoke the method under test with the arranged parameters. 3. Assert: Validate that the outputs of the invoked method behave as expected.
The above unit test method follows this pattern.
- Lines 22-51: The Arrange section is defined. Since the ListUsers() method of the UserService returns a BaseResponse type with a List<UserModel> as data, we mock this type of output from userServiceMock.
- Lines 25-44: Dummy user data is defined to replicate the actual data stored in the database.
- Line 47: A BaseResponse with a success response is established, containing the dummy user data to mimic the return value of the UserService ListUsers() method.
- Line 48: The userServiceMock is set up to return the BaseResponse defined in line 47 as the return value of the ListUsers() method.
- Line 51: An instance of UserController is created, passing in an instance from userServiceMock.
- Lines 53-54: The Act section calls the ListUsers() method from UserController.
- Lines 57-65: The Assert section checks that the output of the ListUsers() method is as expected:
- Line 59: Validates the return type.
- Line 62: Tests the statusCode value of the BaseResponse.
- Line 65: Checks the data type of the BaseResponse's data part.
You have the freedom to define your tests based on the specific scenarios being evaluated in the unit test method.
Step 6
Run your test and review the results. Click on the Test section in the title bar in Visual Studio and select Test Explorer from the menu.
Execute the test and observe the result.
You can similarly implement additional unit tests. The figures below display the service method and its corresponding unit test.
In Figure 20, I used the DisplayName property in the Fact attribute, allowing me to assign meaningful names to the unit test methods. By default, xUnit uses the unit test method name as the display name.
Let’s execute all tests and check the results.
You can implement unit tests for your web API applications similarly.
Unit Tests for Regular Classes
In this section, we will examine how to implement unit tests for standard class methods. I will demonstrate using an example class called ArithmeticHelper, which features two methods: Add(List<double> numbers) and Subtract(double number1, double number2).
The Add() method is designed to sum the values in the list and return the result, while the Subtract() method subtracts number 2 from number 1 and returns the difference. Below are the unit tests for these methods.
The figure below displays the test results for the ArithmeticHelper class.
Notice that the unit test titled "Add numbers should return double type output" has failed. The expectation was for the Add() method to return a double type, but the implementation returns an integer. This illustrates how unit tests can help identify discrepancies between method implementations and application requirements.
That concludes this article. You can access the project through this GitHub repository.
Have a great day!
References
Run unit tests by using Test Explorer - Visual Studio (Windows)
Learn how Visual Studio Test Explorer provides a flexible and efficient way to run your unit tests and view their outcomes.
https://learn.microsoft.com/en-us/dotnet/core/testing
Unit testing C# code in .NET using dotnet test and xUnit - .NET
Discover unit test concepts in C# and .NET through an interactive experience as you build a sample solution step-by-step.
https://learn.microsoft.com/en-us/dotnet/core/testing