Back to: ASP.NET Core Web API Tutorials
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:
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.