Asynchronous Tests using xUnit Framework in ASP.NET Core

Asynchronous Tests using xUnit Framework in ASP.NET Core

In this article, I will discuss how to implement Asynchronous Tests using the xUnit Framework in the ASP.NET Core Web API Application, along with examples. Please read our previous article discussing how to use xUnit Framework Theory Attribute in ASP.NET Core Application with Examples.

Asynchronous Test using xUnit Framework in ASP.NET Core

Asynchronous testing using the xUnit framework in ASP.NET Core refers to the process of writing unit tests that can handle asynchronous code. These tests use the async and await keywords to test methods that perform asynchronous operations, ensuring the code behaves correctly under asynchronous conditions.

This is useful when dealing with operations involving I/O-bound work, such as database calls, file operations, or network requests, where you can use async-await patterns in C#. Asynchronous tests are designed to simulate the behavior of your asynchronous methods, ensuring that they function as expected without blocking the main execution thread.

How to Use Asynchronous Testing Using xUnit Framework in ASP.NET Core

Using asynchronous testing with xUnit in ASP.NET Core involves a few key steps:

  • Mark the Test Method as async: Use the async keyword in the test method signature.
  • Return Task: The return type of the test method should be Task.
  • Use await: Use the await keyword to wait for the completion of asynchronous operations within the test method.

Let us proceed and see how we can implement asynchronous test methods using the xUnit Framework. We will work with the same example we have worked on so far and convert it to use Asynchronous Programming.

Modify the IUserRepository

First, modify the IUserRepository.cs interface of our Web API project as follows. Here, we are marking the method as async by using the return type as Task or Task<T>.

using MyAPI.Models;

namespace MyAPI.Models
{
    public interface IUserRepository
    {
        Task<User> GetUserByIdAsync(int userId);
        Task<IEnumerable<User>> GetAllUsersAsync();
        Task AddUserAsync(User user);
        Task UpdateUserAsync(User user);
        Task DeleteUserAsync(int userId);
    }
}
Modify the UserRepository

Next, modify the User Repository class as follows. Here, we provide the asynchronous implementations of the Repository methods. We simulate the async operation by asynchronously delaying it by 1 millisecond.

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 async Task<User> GetUserByIdAsync(int userId)
        {
            //Simulate the async operation using Task.Delay
            await Task.Delay(TimeSpan.FromMilliseconds(1));
            return _users.FirstOrDefault(u => u.Id == userId);
        }

        public async Task<IEnumerable<User>> GetAllUsersAsync()
        {
            //Simulate the async operation using Task.Delay
            await Task.Delay(TimeSpan.FromMilliseconds(1));
            return _users;
        }

        public async Task AddUserAsync(User user)
        {
            //Simulate the async operation using Task.Delay
            await Task.Delay(TimeSpan.FromMilliseconds(1));
            _users.Add(user);
        }

        public async Task UpdateUserAsync(User user)
        {
            //Simulate the async operation using Task.Delay
            await Task.Delay(TimeSpan.FromMilliseconds(1));
            var existingUser = await GetUserByIdAsync(user.Id);
            if (existingUser != null)
            {
                existingUser.Name = user.Name;
                existingUser.Email = user.Email;
            }
        }

        public async Task DeleteUserAsync(int userId)
        {
            //Simulate the async operation using Task.Delay
            await Task.Delay(TimeSpan.FromMilliseconds(1));
            var user = await GetUserByIdAsync(userId);
            if (user != null)
            {
                _users.Remove(user);
            }
        }
    }
}
Modify the IServiceRepository

Similar to the IUserRepository.cs interface, please update all the IServiceRepository method signatures to be asynchronous. Modify the IServiceRepository interface as follows.

namespace MyAPI.Models
{
    public interface IUserService
    {
        Task<User> GetUserByIdAsync(int userId);
        Task<IEnumerable<User>> GetAllUsersAsync();
        Task AddUserAsync(User user);
        Task UpdateUserAsync(User user);
        Task DeleteUserAsync(int userId);
    }
}
Modify the Service Repository

Next, modify the ServiceRepository class as follows. Here, we provide the asynchronous implementations of the user service methods.

namespace MyAPI.Models
{
    public class UserService : IUserService
    {
        private readonly IUserRepository _userRepository;

        public UserService(IUserRepository userRepository)
        {
            _userRepository = userRepository;
        }

        public async Task<User> GetUserByIdAsync(int userId)
        {
            //Any Business Logic
            return await _userRepository.GetUserByIdAsync(userId);
        }

        public async Task<IEnumerable<User>> GetAllUsersAsync()
        {
            //Any Business Logic
            return await _userRepository.GetAllUsersAsync();
        }

        public async Task AddUserAsync(User user)
        {
            //Any Business Logic
            await _userRepository.AddUserAsync(user);
        }

        public async Task UpdateUserAsync(User user)
        {
            //Any Business Logic
            await _userRepository.UpdateUserAsync(user);
        }

        public async Task DeleteUserAsync(int userId)
        {
            //Any Business Logic
            await _userRepository.DeleteUserAsync(userId);
        }
    }
}
Modify the UsersController

Next, modify the UsersController as follows. Here, we are also marking the action method as asynchronous.

using Microsoft.AspNetCore.Mvc;
using MyAPI.Models;

namespace MyAPI.Controllers
{
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class UsersController : ControllerBase
    {
        private readonly IUserService _userService;

        public UsersController(IUserService userService)
        {
            _userService = userService;
        }

        [HttpGet]
        public async Task<IActionResult> GetAllUsersAsync()
        {
            var users = await _userService.GetAllUsersAsync();
            return Ok(users);
        }

        [HttpGet("{id}")]
        public async Task<IActionResult> GetUserByIdAsync(int id)
        {
            var user = await _userService.GetUserByIdAsync(id);
            if (user == null)
            {
                return NotFound();
            }
            return Ok(user);
        }

        [HttpPost]
        public async Task<IActionResult> AddUserAsync([FromBody] User user)
        {
            await _userService.AddUserAsync(user);
            return CreatedAtAction(nameof(GetUserByIdAsync), new { id = user.Id }, user);
        }

        [HttpPut("{id}")]
        public async Task<IActionResult> UpdateUserAsync(int id, [FromBody] User user)
        {
            if (id != user.Id)
            {
                return BadRequest();
            }

            await _userService.UpdateUserAsync(user);
            return NoContent();
        }

        [HttpDelete("{id}")]
        public async Task<IActionResult> DeleteUserAsync(int id)
        {
            await _userService.DeleteUserAsync(id);
            return NoContent();
        }
    }
}
Using Async Test Methods in Test Project:

Next, modify the UserRepositoryTests.cs class file of our xUnit Test Project as follows. Here, you can see we are also marking the method as asynchronous. For the GetUserById_ReturnsExpectedResult method, we are using the Theory Attribute and InlineData Attribute.

using MyAPI.Models; 
namespace MyAPI.xUnitTests
{
    public class UserRepositoryTests
    {
        private readonly UserRepository _userRepository;

        public UserRepositoryTests()
        {
            // Initialize the UserRepository with dummy data
            _userRepository = new UserRepository();
        }

        // Combined test to verify that GetUserById returns the correct user or null if not found
        [Theory]
        [InlineData(1, true)]  // User with ID 1 exists
        [InlineData(99, false)] // User with ID 99 does not exist
        public async Task GetUserById_ReturnsExpectedResult(int userId, bool userExists)
        {
            // Act
            var result = await _userRepository.GetUserByIdAsync(userId);

            // Assert
            if (userExists)
            {
                Assert.NotNull(result);
                Assert.Equal(userId, result.Id);
            }
            else
            {
                Assert.Null(result);
            }
        }

        [Fact]
        public async Task GetAllUsersAsync_ReturnsAllUsers()
        {
            // Act
            var result = await _userRepository.GetAllUsersAsync();

            // Assert
            Assert.NotNull(result);
            Assert.Equal(2, result.Count()); // Assuming there are 2 users initially
        }

        [Fact]
        public async Task AddUserAsync_AddsUserCorrectly()
        {
            // Arrange
            var newUser = new User { Id = 3, Name = "Sam Wilson", Email = "sam@example.com" };

            // Act
            await _userRepository.AddUserAsync(newUser);
            var result = await _userRepository.GetUserByIdAsync(newUser.Id);

            // Assert
            Assert.NotNull(result);
            Assert.Equal(newUser.Id, result.Id);
            Assert.Equal(newUser.Name, result.Name);
            Assert.Equal(newUser.Email, result.Email);
        }

        [Fact]
        public async Task UpdateUserAsync_UpdatesUserCorrectly()
        {
            // Arrange
            var updatedUser = new User { Id = 1, Name = "John Updated", Email = "john.updated@example.com" };

            // Act
            await _userRepository.UpdateUserAsync(updatedUser);
            var result = await _userRepository.GetUserByIdAsync(updatedUser.Id);

            // Assert
            Assert.NotNull(result);
            Assert.Equal(updatedUser.Name, result.Name);
            Assert.Equal(updatedUser.Email, result.Email);
        }

        [Fact]
        public async Task DeleteUserAsync_DeletesUserCorrectly()
        {
            // Arrange
            var userId = 1;

            // Act
            await _userRepository.DeleteUserAsync(userId);
            var result = await _userRepository.GetUserByIdAsync(userId);

            // Assert
            Assert.Null(result);
        }
    }
}
UserRepositoryTests.cs:

Next, modify the UserRepositoryTests.cs class file of our xUnit Test Project as follows. Here, you can see we are also marking the method as asynchronous. For the GetUserById_ReturnsExpectedResult method, we are using Theory Attribute and MemberData Attribute.

using MyAPI.Models; 
using Moq; 

namespace MyAPI.xUnitTests
{
    public class UserServiceTests
    {
        private readonly UserService _userService;
        private readonly Mock<IUserRepository> _mockRepository;

        public UserServiceTests()
        {
            // Create a mock repository
            _mockRepository = new Mock<IUserRepository>();
            // Inject the mock repository into the UserService
            _userService = new UserService(_mockRepository.Object);
        }

        [Theory]
        [MemberData(nameof(GetUserByIdData))]
        public async Task GetUserByIdAsync_ReturnsExpectedResult(int userId, User expectedUser)
        {
            // Arrange
            _mockRepository.Setup(repo => repo.GetUserByIdAsync(userId)).ReturnsAsync(expectedUser);

            // Act
            var result = await _userService.GetUserByIdAsync(userId);

            // Assert
            if (expectedUser != null)
            {
                Assert.NotNull(result);
                Assert.Equal(expectedUser.Id, result.Id);
                Assert.Equal(expectedUser.Name, result.Name);
                Assert.Equal(expectedUser.Email, result.Email);
            }
            else
            {
                Assert.Null(result);
            }
        }

        [Fact]
        public async Task GetAllUsersAsync_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.GetAllUsersAsync()).ReturnsAsync(expectedUsers);

            // Act
            var result = await _userService.GetAllUsersAsync();

            // Assert
            Assert.NotNull(result);
            Assert.Equal(expectedUsers.Count, result.Count());
        }

        [Fact]
        public async Task AddUserAsync_CallsRepository()
        {
            // Arrange
            var newUser = new User { Id = 3, Name = "Sam Wilson", Email = "sam@example.com" };

            // Act
            await _userService.AddUserAsync(newUser);

            // Assert
            _mockRepository.Verify(repo => repo.AddUserAsync(newUser), Times.Once);
        }

        [Fact]
        public async Task UpdateUserAsync_CallsRepository()
        {
            // Arrange
            var updatedUser = new User { Id = 1, Name = "John Updated", Email = "john.updated@example.com" };

            // Act
            await _userService.UpdateUserAsync(updatedUser);

            // Assert
            _mockRepository.Verify(repo => repo.UpdateUserAsync(updatedUser), Times.Once);
        }

        [Fact]
        public async Task DeleteUserAsync_CallsRepository()
        {
            // Arrange
            var userId = 1;

            // Act
            await _userService.DeleteUserAsync(userId);

            // Assert
            _mockRepository.Verify(repo => repo.DeleteUserAsync(userId), Times.Once);
        }

        // Data provider for GetUserByIdAsync test cases
        public static IEnumerable<object[]> GetUserByIdData =>
            new List<object[]>
            {
                new object[] { 1, new User { Id = 1, Name = "John Doe", Email = "john@example.com" } },
                new object[] { 99, null }
            };
    }
}
UsersControllerTests

Next, modify the UsersControllerTests class file of our xUnit Test Project as follows. Here, you can see we are also marking the method as asynchronous. For the GetUserByIdAsync_ReturnsExpectedResult method, we are using the Theory Attribute and ClassData Attribute.

using Microsoft.AspNetCore.Mvc; 
using Moq; 
using MyAPI.Controllers; 
using MyAPI.Models; 

namespace MyAPI.xUnitTests
{
    public class UsersControllerTests
    {
        private readonly UsersController _controller;
        private readonly Mock<IUserService> _mockService;

        public UsersControllerTests()
        {
            // Create a mock IUserService
            _mockService = new Mock<IUserService>();
            // Inject the mock service into the controller
            _controller = new UsersController(_mockService.Object);
        }

        [Fact]
        public async Task GetAllUsersAsync_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" }
            };
            _mockService.Setup(service => service.GetAllUsersAsync()).ReturnsAsync(expectedUsers);

            // Act
            var result = await _controller.GetAllUsersAsync() as OkObjectResult;

            // Assert
            Assert.NotNull(result);
            Assert.Equal(200, result.StatusCode);
            Assert.Equal(expectedUsers, result.Value);
        }

        [Theory]
        [ClassData(typeof(GetUserByIdTestData))]
        public async Task GetUserByIdAsync_ReturnsExpectedResult(int userId, bool userExists)
        {
            // Arrange
            var expectedUser = userExists ? new User { Id = userId, Name = "Test User", Email = "test@example.com" } : null;
            _mockService.Setup(service => service.GetUserByIdAsync(userId)).ReturnsAsync(expectedUser);

            // Act
            var result = await _controller.GetUserByIdAsync(userId);

            // Assert
            if (userExists)
            {
                var okResult = Assert.IsType<OkObjectResult>(result);
                Assert.NotNull(okResult.Value);
                Assert.Equal(userId, ((User)okResult.Value).Id);
            }
            else
            {
                Assert.IsType<NotFoundResult>(result);
            }
        }

        [Fact]
        public async Task AddUserAsync_ReturnsCreatedAtActionResult()
        {
            // Arrange
            var newUser = new User { Id = 3, Name = "Sam Wilson", Email = "sam@example.com" };
            _mockService.Setup(service => service.AddUserAsync(newUser)).Returns(Task.CompletedTask);

            // Act
            var result = await _controller.AddUserAsync(newUser) as CreatedAtActionResult;

            // Assert
            Assert.NotNull(result);
            Assert.Equal(201, result.StatusCode);
            Assert.Equal("GetUserByIdAsync", result.ActionName);
            Assert.Equal(newUser.Id, ((User)result.Value).Id);
        }

        [Fact]
        public async Task UpdateUserAsync_ReturnsNoContent()
        {
            // Arrange
            var updatedUser = new User { Id = 1, Name = "John Updated", Email = "john.updated@example.com" };
            _mockService.Setup(service => service.UpdateUserAsync(updatedUser)).Returns(Task.CompletedTask);

            // Act
            var result = await _controller.UpdateUserAsync(1, updatedUser) as NoContentResult;

            // Assert
            Assert.NotNull(result);
            Assert.Equal(204, result.StatusCode);
        }

        [Fact]
        public async Task UpdateUserAsync_ReturnsBadRequest_WhenIdMismatch()
        {
            // Arrange
            var updatedUser = new User { Id = 2, Name = "John Updated", Email = "john.updated@example.com" };

            // Act
            var result = await _controller.UpdateUserAsync(1, updatedUser) as BadRequestResult;

            // Assert
            Assert.NotNull(result);
            Assert.Equal(400, result.StatusCode);
        }

        [Fact]
        public async Task DeleteUserAsync_ReturnsNoContent()
        {
            // Arrange
            var userId = 1;
            _mockService.Setup(service => service.DeleteUserAsync(userId)).Returns(Task.CompletedTask);

            // Act
            var result = await _controller.DeleteUserAsync(userId) as NoContentResult;

            // Assert
            Assert.NotNull(result);
            Assert.Equal(204, result.StatusCode);
        }
    }
}

Now, test all cases, and it should work as expected, as shown in the below image:

How to Implement Asynchronous Tests using the xUnit Framework in the ASP.NET Core Application with Examples

Points to Remember:
  • What is Asynchronous Testing: Writing test methods that handle asynchronous operations using async and await in the xUnit framework.
  • Why Asynchronous Testing: To accurately test modern applications that use asynchronous programming, handle concurrency correctly, and ensure performance and accuracy.
  • How to Use Asynchronous Testing: Mark test methods as async, return Task, and use await to handle asynchronous operations within the test methods.

In the next article, I will discuss How to use Fluent Assertions with xUnit Framework in ASP.NET Core Web API Application with Examples. In this article, I explain How to Implement Asynchronous Tests using the xUnit Framework in the ASP.NET Core Application with Examples. I hope you enjoy this Asynchronous Test using the xUnit Framework in an ASP.NET Core Application article.

Leave a Reply

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