Back to: ASP.NET Core Web API Tutorials
Unit Testing in ASP.NET Core Web API using xUnit Framework
In this article, I will discuss how to Implement Unit Testing in an ASP.NET Core Web API Project using xUnit Framework with Examples. Please read our previous article discussing What unit testing is in ASP.NET Core and why we need Unit Testing.
Example to Understand xUnit Testing Framework in ASP.NET Core Web API:
First, we will create a new ASP.NET Core Web API Project. Within the Project, we will create a Controller to expose the API Endpoints to perform the CRUD operations on User Entity, a service class which will be consumed by the Controller, a repository class which will be consumed by the Service class. Then, we will see how we can unit the individual components, i.e., how we can test the repository class methods, service class method, and controller class methods. Let us proceed and implement this step by step:
Create a New ASP.NET Core Web API Project
- Open Visual Studio 2022.
- Select Create a new project.
- Choose ASP.NET Core Web API and click Next.
- Name your project (e.g., MyAPI), choose a location, and click Next.
- Choose .NET 8.0 as the framework, and click Create.
This should be the ASP.NET Core Web API Project with the following structure:
Creating the Model Class:
We are going to perform the CRUD Operations on the User entity. So, first, create a folder named Models at the project root directory, and then within the Models folder, create a class file named User.cs and then copy and paste the following code:
namespace MyAPI.Models { public class User { public int Id { get; set; } public string Name { get; set; } public string Email { get; set; } } }
Create a Repository Interface
Next, we need to create the Repository interface where we declare the operations that can be performed on the User entity. So, create an interface file named IUserRepository.cs within the Models folder and copy and paste the following code. The following interface declares the typical CRUD operations.
namespace MyAPI.Models { public interface IUserRepository { User GetUserById(int userId); IEnumerable<User> GetAllUsers(); void AddUser(User user); void UpdateUser(User user); void DeleteUser(int userId); } }
Implementing IUserRepository Interface:
Next, we need to create a class implementing the IUserRepository Interface. So, create a class file named UserRepository.cs within the Models folder and copy and paste the following code. Here, you can see that we are providing implementations to all the IUserRepository Interface methods.
namespace MyAPI.Models { public class UserRepository : IUserRepository { private readonly List<User> _users; public UserRepository() { // Simulating a data source with some dummy data _users = new List<User> { new User { Id = 1, Name = "John Doe", Email = "john@example.com" }, new User { Id = 2, Name = "Jane Smith", Email = "jane@example.com" } }; } public User GetUserById(int userId) { return _users.FirstOrDefault(u => u.Id == userId); } public IEnumerable<User> GetAllUsers() { return _users; } public void AddUser(User user) { _users.Add(user); } public void UpdateUser(User user) { var existingUser = GetUserById(user.Id); if (existingUser != null) { existingUser.Name = user.Name; existingUser.Email = user.Email; } } public void DeleteUser(int userId) { var user = GetUserById(userId); if (user != null) { _users.Remove(user); } } } }
Registering the User Repository:
Please add the following code to the Program class to add the User Repository to the built-in dependency injection container.
// Add Repository to the container. builder.Services.AddSingleton<IUserRepository, UserRepository>();
Create a Service Interface
Next, we need to create the Service interface where we declare the operations. So, create an interface file named IUserService.cs within the Models folder and copy and paste the following code.
namespace MyAPI.Models { public interface IUserService { User GetUserById(int userId); IEnumerable<User> GetAllUsers(); void AddUser(User user); void UpdateUser(User user); void DeleteUser(int userId); } }
Creating the Service Class:
Next, we need to create the Service class that will be consumed by the Controller. Inside this service class, we can implement the business logic, and then we need to call Repository class methods to save the data, get the data, update the data, and delete the user data. So, create a class file named UserService.cs within the Models folder and copy and paste the following code. This class should implement the IUserService interface.
namespace MyAPI.Models { public class UserService : IUserService { private readonly IUserRepository _userRepository; public UserService(IUserRepository userRepository) { _userRepository = userRepository; } public User GetUserById(int userId) { //Any Business Logic return _userRepository.GetUserById(userId); } public IEnumerable<User> GetAllUsers() { //Any Business Logic return _userRepository.GetAllUsers(); } public void AddUser(User user) { //Any Business Logic _userRepository.AddUser(user); } public void UpdateUser(User user) { //Any Business Logic _userRepository.UpdateUser(user); } public void DeleteUser(int userId) { //Any Business Logic _userRepository.DeleteUser(userId); } } }
Registering the User Service:
Please add the following code to the Program class to add the User Service to the built-in dependency injection container.
// Add User Service to the container. builder.Services.AddSingleton<IUserService, UserService>();
Creating Controller:
Next, we need to create the API Controller, which will use the User Service. This Controller will expose the endpoint to be consumed by the client. So, create a new API Empty Controller named UsersController within the Controllers folder and copy and paste the following code.
using Microsoft.AspNetCore.Mvc; using MyAPI.Models; namespace MyAPI.Controllers { [Route("api/[controller]/[action]")] [ApiController] public class UsersController : ControllerBase { private readonly UserService _userService; public UsersController(UserService userService) { _userService = userService; } [HttpGet] public IActionResult GetAllUsers() { var users = _userService.GetAllUsers(); return Ok(users); } [HttpGet("{id}")] public IActionResult GetUserById(int id) { var user = _userService.GetUserById(id); if (user == null) { return NotFound(); } return Ok(user); } [HttpPost] public IActionResult AddUser([FromBody] User user) { _userService.AddUser(user); return CreatedAtAction(nameof(GetUserById), new { id = user.Id }, user); } [HttpPut("{id}")] public IActionResult UpdateUser(int id, [FromBody] User user) { if (id != user.Id) { return BadRequest(); } _userService.UpdateUser(user); return NoContent(); } [HttpDelete("{id}")] public IActionResult DeleteUser(int id) { _userService.DeleteUser(id); return NoContent(); } } }
Configuring a Test Project Using xUnit Framework
In the Solution Explorer, right-click on the Solution and select Add > New Project. Choose xUnit Test Project and click Next, as shown in the below image.
Once you click on the Next button, the Configure your New Project window will open. Here, name the project MyAPI.xUnitTests, choose a location, and click the Create button, as shown in the image below.
Once you click on the Next button, the Additional Information window will open. Here, select the .NET Version and click on the Create button, as shown in the image below.
Once you click the Create button, the Test project will be created within the same solution as shown in the image below.
Add Project References
We are going to test the individual components of the MyAPI Project, so we need to add the reference of the MyAPI Project from our MyAPI.Tests project. To do so, right-click on the MyAPI.Tests project, select Add > Project Reference, which will open the Reference Manager window. Here, please check the box next to MyAPI and click the OK button, as shown in the image below.
Once you click on the OK button, it should add the Project reference, which you can verify within the Dependencies folder, as shown in the image below.
Installing the Moq Package:
Please install the Moq package from Nuget. This is required for testing our services. You can install it using the Package Manager Console using the Install-Package Moq command, as shown in the image below. Please select the project as MyAPI.xUnitTests.
Writing Test Cases Using xUnit Framework
Now, we will write test cases to test each individual component. I will show you how to test User Repository class methods, User Service Class methods, and User Controller class methods. The Fact attribute in xUnit is used to denote a method as a test method. When we decorate a method with the Fact attribute, xUnit knows that this method should be executed as a test case. A test method marked with Fact does not take any parameters.
Unit Testing UserRepository:
In the MyAPI.Tests project, add a new class UserRepositoryTests.cs and copy and paste the following code.
// Importing the Models namespace which likely contains the UserRepository and User classes using MyAPI.Models; namespace MyAPI.xUnitTests { public class UserRepositoryTests { private readonly UserRepository _userRepository; // Constructor initializes the UserRepository instance public UserRepositoryTests() { _userRepository = new UserRepository(); } // Test to verify that GetUserById returns the correct user [Fact] public void GetUserById_ReturnsCorrectUser() { // Arrange var userId = 1; // Act var result = _userRepository.GetUserById(userId); // Assert // Check that result is not null Assert.NotNull(result); // Check that the ID of the returned user is correct Assert.Equal(userId, result.Id); } // Test to verify that GetUserById returns null when the user is not found [Fact] public void GetUserById_ReturnsNullWhenUserNotFound() { // Arrange // Assuming this ID does not exist var userId = 99; // Act var result = _userRepository.GetUserById(userId); // Assert // Check that result is null Assert.Null(result); } // Test to verify that GetAllUsers returns all users [Fact] public void GetAllUsers_ReturnsAllUsers() { // Act var result = _userRepository.GetAllUsers(); // Assert // Check that result is not null Assert.NotNull(result); // Assuming there are 2 users, check that the count is correct Assert.Equal(2, result.Count()); } // Test to verify that AddUser adds a user correctly [Fact] public void AddUser_AddsUserCorrectly() { // Arrange var newUser = new User { Id = 3, Name = "Sam Wilson", Email = "sam@example.com" }; // Act _userRepository.AddUser(newUser); var result = _userRepository.GetUserById(3); // Assert Assert.NotNull(result); // Check that the user was added and returned Assert.Equal(newUser.Id, result.Id); // Check that the ID is correct Assert.Equal(newUser.Name, result.Name); // Check that the name is correct Assert.Equal(newUser.Email, result.Email); // Check that the email is correct } // Test to verify that UpdateUser updates a user correctly [Fact] public void UpdateUser_UpdatesUserCorrectly() { // Arrange var updatedUser = new User { Id = 1, Name = "John Updated", Email = "john.updated@example.com" }; // Act _userRepository.UpdateUser(updatedUser); var result = _userRepository.GetUserById(1); // Assert Assert.NotNull(result); // Check that the user was found Assert.Equal(updatedUser.Name, result.Name); // Check that the name was updated Assert.Equal(updatedUser.Email, result.Email); // Check that the email was updated } // Test to verify that DeleteUser deletes a user correctly [Fact] public void DeleteUser_DeletesUserCorrectly() { // Arrange var userId = 1; // Act _userRepository.DeleteUser(userId); var result = _userRepository.GetUserById(userId); // Assert Assert.Null(result); // Check that the user was deleted and cannot be found } } }
Here,
- Arrange means setting up any necessary data or state before performing the action.
- Act means performing the action to be tested.
- Assert means verifying that the action produced the expected results.
Unit Testing UserServiceÂ
In the MyAPI.Tests project, add a new class UserServiceTests.cs and copy and paste the following code.
using MyAPI.Models; // Importing Moq for creating mock objects using Moq; namespace MyAPI.xUnitTests { public class UserServiceTests { // Instance of the service being tested private readonly UserService _demoService; // Mock instance of the repository private readonly Mock<IUserRepository> _mockRepository; // Constructor to initialize mock repository and the service public UserServiceTests() { _mockRepository = new Mock<IUserRepository>(); _demoService = new UserService(_mockRepository.Object); } // Test to verify that GetUserById returns the correct user [Fact] public void GetUserById_ReturnsUser() { // Arrange var userId = 1; var expectedUser = new User { Id = 1, Name = "John Doe", Email = "john@example.com" }; _mockRepository.Setup(repo => repo.GetUserById(userId)).Returns(expectedUser); // Act var result = _demoService.GetUserById(userId); // Assert Assert.NotNull(result); // Check that the result is not null Assert.Equal(expectedUser.Id, result.Id); // Check that the ID is correct Assert.Equal(expectedUser.Name, result.Name); // Check that the name is correct Assert.Equal(expectedUser.Email, result.Email); // Check that the email is correct } // Test to verify that GetUserById returns null when the user is not found [Fact] public void GetUserById_ReturnsNullWhenUserNotFound() { // Arrange var userId = 99; _mockRepository.Setup(repo => repo.GetUserById(userId)).Returns((User)null); // Act var result = _demoService.GetUserById(userId); // Assert Assert.Null(result); // Check that the result is null } // Test to verify that GetAllUsers returns a list of users [Fact] public void GetAllUsers_ReturnsListOfUsers() { // Arrange var expectedUsers = new List<User> { new User { Id = 1, Name = "John Doe", Email = "john@example.com" }, new User { Id = 2, Name = "Jane Smith", Email = "jane@example.com" } }; _mockRepository.Setup(repo => repo.GetAllUsers()).Returns(expectedUsers); // Act var result = _demoService.GetAllUsers(); // Assert Assert.NotNull(result); // Check that the result is not null // Check that the count of users is correct Assert.Equal(expectedUsers.Count, result.Count()); } // Test to verify that AddUser calls the repository's AddUser method [Fact] public void AddUser_CallsRepository() { // Arrange var newUser = new User { Id = 3, Name = "Sam Wilson", Email = "sam@example.com" }; // Act _demoService.AddUser(newUser); // Assert // Check that AddUser was called once _mockRepository.Verify(repo => repo.AddUser(newUser), Times.Once); } // Test to verify that UpdateUser calls the repository's UpdateUser method [Fact] public void UpdateUser_CallsRepository() { // Arrange var updatedUser = new User { Id = 1, Name = "John Updated", Email = "john.updated@example.com" }; // Act _demoService.UpdateUser(updatedUser); // Assert // Check that UpdateUser was called once _mockRepository.Verify(repo => repo.UpdateUser(updatedUser), Times.Once); } // Test to verify that DeleteUser calls the repository's DeleteUser method [Fact] public void DeleteUser_CallsRepository() { // Arrange var userId = 1; // Act _demoService.DeleteUser(userId); // Assert // Check that DeleteUser was called once _mockRepository.Verify(repo => repo.DeleteUser(userId), Times.Once); } } }
Here,
- Arrange: Set up any necessary data or state before performing the action. This often includes configuring the mock repository to return specific data.
- Act: Perform the action to be tested. This typically involves calling a method on the service being tested.
- Assert: Verify that the action produced the expected results. This includes checking return values and ensuring that certain methods were called on the mock objects.
What is the use of Mock Object?
Here, _mockRepository is an instance of Mock<IUserRepository>, created using the Moq library. This mock object is used to simulate the behavior of the IUserRepository interface without relying on a real implementation. This is helpful in unit tests to isolate the code under test and control the behavior of dependencies.
In our example, the User Service will use the Mock repository instead of the real user Repository instance. Please run the test cases using debug mode to better understand this concept.
- The Setup method configures the behavior of a mock object. It specifies what should happen when a particular method on the mocked interface is called.
- The Returns method is used in conjunction with the Setup method to specify the return value of a mocked method call. It tells the mock what value to return when the method specified in Setup is called.
- The Verify method confirms that specific interactions with the mock object have occurred. It checks that certain methods were called with the expected parameters and the expected number of times during the test execution.
Testing UsersController
In the MyAPI.Tests project, add a new class UsersControllerTests.cs and then copy and paste the following code.
using Microsoft.AspNetCore.Mvc; using Moq; using MyAPI.Controllers; using MyAPI.Models; namespace MyAPI.xUnitTests { public class UsersControllerTests { // Instance of the controller being tested private readonly UsersController _controller; // Mock instance of the service private readonly Mock<IUserService> _mockService; // Constructor to initialize mock service and the controller public UsersControllerTests() { // Creating a mock object for IUserService _mockService = new Mock<IUserService>(); // Injecting the mock object into the controller _controller = new UsersController(_mockService.Object); } // Test to verify that GetUserById returns an OkObjectResult with the correct user [Fact] public void GetUser_ReturnsOkResultWithUser() { // Arrange var userId = 1; // Define the user ID to retrieve var expectedUser = new User { Id = 1, Name = "John Doe", Email = "john@example.com" }; // Define the expected user _mockService.Setup(service => service.GetUserById(userId)).Returns(expectedUser); // Configure the mock service to return the expected user // Act var result = _controller.GetUserById(userId) as OkObjectResult; // Call the controller method and cast the result to OkObjectResult // Assert Assert.NotNull(result); // Check that the result is not null Assert.Equal(200, result.StatusCode); // Check that the status code is 200 OK Assert.Equal(expectedUser, result.Value); // Check that the returned value is the expected user } // Test to verify that GetUserById returns a NotFoundResult when the user is not found [Fact] public void GetUser_ReturnsNotFoundWhenUserNotFound() { // Arrange var userId = 99; // Define a non-existent user ID _mockService.Setup(service => service.GetUserById(userId)).Returns((User)null); // Configure the mock service to return null // Act var result = _controller.GetUserById(userId) as NotFoundResult; // Call the controller method and cast the result to NotFoundResult // Assert Assert.NotNull(result); // Check that the result is not null Assert.Equal(404, result.StatusCode); // Check that the status code is 404 Not Found } // Test to verify that GetAllUsers returns an OkObjectResult with a list of users [Fact] public void GetAllUsers_ReturnsOkResultWithListOfUsers() { // Arrange var expectedUsers = new List<User> { new User { Id = 1, Name = "John Doe", Email = "john@example.com" }, new User { Id = 2, Name = "Jane Smith", Email = "jane@example.com" } }; // Define the expected list of users _mockService.Setup(service => service.GetAllUsers()).Returns(expectedUsers); // Configure the mock service to return the expected list of users // Act var result = _controller.GetAllUsers() as OkObjectResult; // Call the controller method and cast the result to OkObjectResult // Assert Assert.NotNull(result); // Check that the result is not null Assert.Equal(200, result.StatusCode); // Check that the status code is 200 OK Assert.Equal(expectedUsers, result.Value); // Check that the returned value is the expected list of users } // Test to verify that AddUser returns a CreatedAtActionResult [Fact] public void AddUser_ReturnsCreatedAtAction() { // Arrange var newUser = new User { Id = 3, Name = "Sam Wilson", Email = "sam@example.com" }; // Define the new user to add // Act var result = _controller.AddUser(newUser) as CreatedAtActionResult; // Call the controller method and cast the result to CreatedAtActionResult // Assert Assert.NotNull(result); // Check that the result is not null Assert.Equal(201, result.StatusCode); // Check that the status code is 201 Created Assert.Equal("GetUserById", result.ActionName); // Check that the action name is "GetUserById" Assert.Equal(newUser.Id, ((User)result.Value).Id); // Check that the returned user ID is the new user's ID } // Test to verify that UpdateUser returns a NoContentResult [Fact] public void UpdateUser_ReturnsNoContent() { // Arrange var updatedUser = new User { Id = 1, Name = "John Updated", Email = "john.updated@example.com" }; // Define the updated user _mockService.Setup(service => service.UpdateUser(updatedUser)); // Configure the mock service to update the user // Act var result = _controller.UpdateUser(1, updatedUser) as NoContentResult; // Call the controller method and cast the result to NoContentResult // Assert Assert.NotNull(result); // Check that the result is not null Assert.Equal(204, result.StatusCode); // Check that the status code is 204 No Content } // Test to verify that DeleteUser returns a NoContentResult [Fact] public void DeleteUser_ReturnsNoContent() { // Arrange var userId = 1; // Define the user ID to delete _mockService.Setup(service => service.DeleteUser(userId)); // Configure the mock service to delete the user // Act var result = _controller.DeleteUser(userId) as NoContentResult; // Call the controller method and cast the result to NoContentResult // Assert Assert.NotNull(result); // Check that the result is not null Assert.Equal(204, result.StatusCode); // Check that the status code is 204 No Content } } }
Note: In the above code, we also use the Mock User Service object. The Controller class uses the User Service class, but instead of using the actual User Service class data, we use the Mock User Service class.
Testing the Test Cases:
Now, we can test all cases, individual test cases, and all test cases of either Controller, Service, or Repository.
Run All Tests:
To run all test cases, go to the Test menu and select Run All Tests from Visual Studio, as shown in the below image:
This will run all the test cases and display the results in the image below. If you want to run all test cases in debug mode, select the Debug All Tests option from the context menu.
Testing Individual Test Cases:
If you want to test individual test cases, then you need to choose Test => Test Explorer from the context menu, as shown in the below image:
This will open the following Page. Here, you can right-click on Individual Test Cases and click on the Run option to run the test case as shown in the below image:
What is Assert Class?
The Assert class in xUnit provides a set of static methods to verify that conditions are met in your tests. If an assertion fails, the test is marked as failed. It contains various static methods for different types of assertions (e.g., Equal, True, False, Null, Throws). It determines whether a test passes or fails based on the conditions specified in the assertions. Common Assert Methods are as follows:
- Assert.Equal(expected, actual): Verifies that two values are equal.
- Assert.NotEqual(notExpected, actual): Verifies that two values are not equal.
- Assert.True(condition): Verifies that a condition is true. For example, Assert.True(result > 0);
- Assert.False(condition): Verifies that a condition is false. For example, Assert.False(result < 0);
- Assert.Null(object): Verifies that an object is null.
- Assert.NotNull(object): Verifies that an object is not null.
- Assert.Same(expected, actual): Verifies that two object references refer to the same object.
- NotSame: Assert.NotSame(notExpected, actual): Verifies that two object references do not refer to the same object.
- Assert.Empty(collection): Verifies that a collection is empty.
- Assert.NotEmpty(collection): Verifies that a collection is not empty.
In the next article, I will discuss using Theory Attribute with xUnit Framework in ASP.NET Core Web API Project with Examples. In this article, I explain How to Implement Unit Testing in an ASP.NET Core Web API Project using the xUnit Framework with Examples. I hope you enjoy this Unit Testing in the ASP.NET Core Web API Project using the xUnit Framework article.
Registration Open For New Online Training
Enhance Your Professional Journey with Our Upcoming Live Session. For complete information on Registration, Course Details, Syllabus, and to get the Zoom Credentials to attend the free live Demo Sessions, please click on the below links.