# Testing Services Testing services is a important part of .NET devolopment as it helps to ensure that the services are working as expected and also helps to catch bugs early in the development process. It's most likely for a service test to be a unit test, and it's important to test the service in isolation. This means that the service should be tested without any dependencies on external services or databases. For this reason it's important to use dependency injection to inject mock services into the service being tested. This allows the service to be tested in isolation and ensures that the test is repeatable and reliable. This will also require that interfaces are used for the services so that the mock services can be injected into the service being tested. # Example Application In this example we will create a simple Web API to maintain a user list. To get started create a new Web API project titled `UserApp` and enabled controller support. ## User Model and DB Context Create a new `Data` folder and insert the following files: ::: code-group ```csharp [User.cs] namespace UserApp.Data { public class User { public int Id { get; set; } public string Name { get; set; } = string.Empty; public string Email { get; set; } = string.Empty; public string FirstName { get; set; } = string.Empty; public string LastName { get; set; } = string.Empty; } } ``` ::: ::: code-group ```csharp [UserAppContext.cs] using Microsoft.EntityFrameworkCore; namespace UserApp.Data { public class UserAppContext : DbContext { public UserAppContext(DbContextOptions options) : base(options) { } public DbSet Users { get; set; } } } ``` ::: ::: code-group ```csharp [UserViewModel.cs] namespace UserApp.Models { public class UserViewModel(string email, string firstName, string lastName) { public string Email { get; set; } = email; public string FirstName { get; set; } = firstName; public string LastName { get; set; } = lastName; } } ``` ::: As part of this ensure the following NuGet packages are installed: * Microsoft.EntityFrameworkCore ## Service Interface and Implementation First create a entity not found exception: ::: code-group ```csharp [EntityNotFoundException.cs] namespace UserApp.Infrastructure.Exceptions { public class EntityNotFoundException(string message) : Exception(message) { } } ``` ::: Then we can create the service interface and implementation: ::: code-group ```csharp [IUserService.cs] using UserApp.Data; namespace UserApp.Services { public interface IUserService { public Task> GetUsersAsync(); public Task GetUserAsync(int id); public Task AddUserAsync(string email, string firstName, string lastName); public Task UpdateUserAsync(int id, string email, string firstName, string lastName); public Task DeleteUserAsync(int id); } } ``` ```csharp [UserService.cs] using Microsoft.EntityFrameworkCore; using UserApp.Data; using UserApp.Infrastructure.Exceptions; namespace UserApp.Services { public class UserService(UserAppContext context) : IUserService { private readonly UserAppContext _context = context; public async Task AddUserAsync(string email, string firstName, string lastName) { User user = new() { Email = email, FirstName = firstName, LastName = lastName }; await _context.Users.AddAsync(user); await _context.SaveChangesAsync(); return user; } public async Task DeleteUserAsync(int id) { User? user = await _context.Users.FindAsync(id) ?? throw new EntityNotFoundException("User not found"); _context.Users.Remove(user); await _context.SaveChangesAsync(); return user; } public async Task GetUserAsync(int id) { User user = await _context.Users.FindAsync(id) ?? throw new EntityNotFoundException("User not found"); return user; } public async Task> GetUsersAsync() { return await _context.Users.ToListAsync(); } public async Task UpdateUserAsync(int id, string email, string firstName, string lastName) { User user = await _context.Users.FindAsync(id) ?? throw new EntityNotFoundException("User not found"); user.Email = email; user.FirstName = firstName; user.LastName = lastName; await _context.SaveChangesAsync(); return user; } } } ``` ::: ## Setup Dependency Injection We now need to add our DI config to the `Program.cs` file. Add the following to the service section, you will also need to install the following NuGet packages: * Microsoft.EntityFrameworkCore.InMemory ::: code-group ```csharp [Program.cs] builder.Services.AddDbContext(options => { options.UseInMemoryDatabase("UserApp"); }); builder.Services.AddScoped(); ``` ::: ## Controller Finally we can create a controller to interact with the service: ::: code-group ```csharp [UsersController.cs] using Microsoft.AspNetCore.Mvc; using UserApp.Data; using UserApp.Infrastructure.Exceptions; using UserApp.Models; using UserApp.Services; namespace UserApp.Controllers { [Route("api/[controller]")] public class UserController(IUserService userService, ILogger logger) : ControllerBase { private readonly ILogger _logger = logger; private readonly IUserService _userService = userService; [HttpGet] public async Task GetUsersAsync() { try { return Ok(await _userService.GetUsersAsync()); } catch (Exception exception) { _logger.LogError(exception, "An error occurred while getting the users"); return StatusCode(500); } } [HttpGet("{id}")] public async Task GetUserAsync(int id) { try { return Ok(await _userService.GetUserAsync(id)); } catch (EntityNotFoundException exception) { _logger.LogError(exception, "User not found"); return NotFound(); } catch (Exception exception) { _logger.LogError(exception, "An error occurred while getting the user"); return StatusCode(500); } } [HttpPost] public async Task AddUserAsync([FromBody] UserViewModel user) { try { User createdUser = await _userService.AddUserAsync(user.Email, user.FirstName, user.LastName); return CreatedAtAction("GetUser", new { id = createdUser.Id }, createdUser); } catch (Exception exception) { _logger.LogError(exception, "An error occurred while adding the user"); return StatusCode(500); } } [HttpPut("{id}")] public async Task UpdateUserAsync(int id, [FromBody] UserViewModel user) { try { return Ok(await _userService.UpdateUserAsync(id, user.Email, user.FirstName, user.LastName)); } catch (EntityNotFoundException exception) { _logger.LogError(exception, "User not found"); return NotFound(); } catch (Exception exception) { _logger.LogError(exception, "An error occurred while updating the user"); return StatusCode(500); } } [HttpDelete("{id}")] public async Task DeleteUserAsync(int id) { try { return Ok(await _userService.DeleteUserAsync(id)); } catch (EntityNotFoundException exception) { _logger.LogError(exception, "User not found"); return NotFound(); } catch (Exception exception) { _logger.LogError(exception, "An error occurred while deleting the user"); return StatusCode(500); } } } } ``` ::: # Service Testing Now that we have our service and controller setup we can start testing the service. We will be using MSTest and Moq to test the service. Create a new MSTest project titled `UserApp.Service.Test` and then add a new file titled `UserServiceTests.cs`. We will first get started with two tests, to ensure our `GetUsersAsync` method is working as expected. ::: code-group ```csharp [UserServiceTests.cs] using Microsoft.EntityFrameworkCore; using UserApp.Data; using UserApp.Services; namespace UserApp.Service.Test { [TestClass] public class UserServiceTests { private UserAppContext _context = null!; [TestInitialize] public void Initialize() { DbContextOptions options = new DbContextOptionsBuilder() .UseInMemoryDatabase(Guid.NewGuid().ToString()) .Options; _context = new UserAppContext(options); } [TestCleanup] public void Cleanup() { _context.Dispose(); _context = null!; } [TestMethod] public async Task GetUsersAsync_NoUsers_ReturnsNoUsers() { // Arrange UserService service = new(_context); // Act IEnumerable users = await service.GetUsersAsync(); // Assert Assert.AreEqual(0, users.Count()); } [TestMethod] public async Task GetUsersAsync_OneUser_ReturnsOneUser() { // Arrange _context.Users.Add(new User() { Email = "john@test.com", FirstName = "John", LastName = "Smith" }); _context.SaveChanges(); UserService service = new(_context); // Act IEnumerable users = await service.GetUsersAsync(); // Assert Assert.AreEqual(1, users.Count()); Assert.AreEqual("john@test.com", users.First().Email); } } } ``` Run these two tests and confirm that they pass. This will confirm that the `GetUsersAsync` method is working as expected. You can now continue to write tests for the other methods in the `UserService` class. ::: ```csharp [UserServiceTests] // ... [TestMethod] public async Task GetUserAsync_ForExistingUser_ReturnsUser() { // Arrange User user1 = new() { Email = "john@test.com", FirstName = "John", LastName = "Smith" }; User user2 = new() { Email = "jane@test.com", FirstName = "Jane", LastName = "Doe" }; await _context.Users.AddRangeAsync(user1, user2); await _context.SaveChangesAsync(); UserService service = new(_context); // Act User user = await service.GetUserAsync(1); // Assert Assert.AreEqual("john@test.com", user.Email); } [TestMethod] public async Task GetUserAsync_ForNonExistingUserAndNoUsers_ThrowsEntityNotFoundException() { // Arrange UserService service = new(_context); // Act and Assert await Assert.ThrowsExceptionAsync(() => service.GetUserAsync(1)); } [TestMethod] public async Task GetUserAsync_ForNonExistingUserAndUsers_ThrowsEntityNotFoundException() { // Arrange User user1 = new() { Email = "john@test.com", FirstName = "John", LastName = "Smith" }; User user2 = new() { Email = "jane@test.com", FirstName = "Jane", LastName = "Doe" }; await _context.Users.AddRangeAsync(user1, user2); await _context.SaveChangesAsync(); UserService service = new(_context); // Act and Assert await Assert.ThrowsExceptionAsync(() => service.GetUserAsync(3)); } [TestMethod] public async Task AddUserAsync_AddsUser() { // Arrange UserService service = new(_context); // Act int oldCount = _context.Users.Count(); User user = await service.AddUserAsync("john@test.com", "John", "Smith"); int newCount = _context.Users.Count(); // Assert Assert.AreEqual(0, oldCount); Assert.AreEqual(1, newCount); Assert.AreEqual("john@test.com", _context.Users.First().Email); } [TestMethod] public async Task UpdateUserAsync_CanUpdateExistingUser_UserUpdated() { // Arrange User user1 = new() { Email = "john@test.com", FirstName = "John", LastName = "Smith" }; User user2 = new() { Email = "jane@test.com", FirstName = "Jane", LastName = "Doe" }; await _context.Users.AddRangeAsync(user1, user2); await _context.SaveChangesAsync(); UserService service = new(_context); // Act await service.UpdateUserAsync(1, "johnnew@test.com", "JohnNew", "SmithNew"); // Assert User updatedUser = (await _context.Users.FindAsync(1))!; Assert.AreEqual("johnnew@test.com", updatedUser.Email); Assert.AreEqual("JohnNew", updatedUser.FirstName); Assert.AreEqual("SmithNew", updatedUser.LastName); } public async Task UpdateUserAsync_ForNonExistingUser_ThrowsEntityNotFoundException() { // Arrange User user1 = new() { Email = "john@test.com", FirstName = "John", LastName = "Smith" }; User user2 = new() { Email = "jane@test.com", FirstName = "Jane", LastName = "Doe" }; await _context.Users.AddRangeAsync(user1, user2); await _context.SaveChangesAsync(); UserService service = new(_context); // Act and Assert await Assert.ThrowsExceptionAsync(() => service.UpdateUserAsync(3, "", "", "")); } [TestMethod] public async Task DeleteUserAsync_ForExistingUser_RemovesUser() { // Arrange User user1 = new() { Email = "john@test.com", FirstName = "John", LastName = "Smith" }; await _context.Users.AddAsync(user1); await _context.SaveChangesAsync(); UserService service = new(_context); // Act int oldCount = _context.Users.Count(); User user = await service.DeleteUserAsync(1); int newCount = _context.Users.Count(); // Assert Assert.AreEqual(1, oldCount); Assert.AreEqual(0, newCount); Assert.AreEqual("john@test.com", user.Email); } [TestMethod] public async Task DeleteUserAsync_ForNonExistingUser_ThrowsEntityNotFoundException() { // Arrange UserService service = new(_context); // Act and Assert await Assert.ThrowsExceptionAsync(() => service.DeleteUserAsync(1)); } // ... ``` # Conclusion This this example we have created a simple Web API, and tested the service using MSTest. This is a good starting point for testing services in .NET and can be expanded upon to test more complex services.