Unit Testing in ASP.NET Core Web API using xUnit Framework

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:

Create a New ASP.NET Core Web API Project

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.

Example to Understand xUnit Testing Framework in ASP.NET Core Web API

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.

Configuring a Test Project Using xUnit Framework

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.

how to Implement Unit Testing in an ASP.NET Core Web API Project using xUnit Framework with Examples

Once you click the Create button, the Test project will be created within the same solution as shown in the image below.

Unit Testing in an ASP.NET Core Web API Project using xUnit Framework with Examples

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.

Unit Testing in an ASP.NET Core Web API Project using xUnit Framework

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.

blank

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.

Installing the Moq Package

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:

Run All Tests

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.

Run All Tests

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:

Testing Individual Test Cases

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:

Testing Individual Test Cases

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.

Leave a Reply

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