In the previous articles of this Clean Architecture C# example series, we implemented the Domain, Application, Infrastructure, and Presentation layers, configured database migrations, and tested the API endpoints using Swagger.
While Swagger helps us manually test API endpoints an d verify application functionality, unit testing allows us to automatically verify individual components of the application. It helps identify issues early, improves code quality, and makes the application easier to maintain.
In this article, we will implement unit testing in our existing CleanArchitectureDemo project using xUnit and Moq. We will create a separate test project, write unit tests for the the EmployeeService, mock the repository dependencies, and verify different scenarios such as retrieving, creating, updating, and deleting employees.
By the end of this article, you will understand how to write and execute unit tests in an ASP.NET Core Web API application following Clean Architecture principles.
ASP.NET Clean Architecture: Unit Testing Using xUnit and Moq
Getting Started
Here, we will implement unit testing in our existing CleanArchitectureDemo project using xUnit and Moq. We will create a separate test project and write unit tests for the EmployeeService. During testing, we will mock the IEmployeeRepository dependency so that the tests focus only on the service logic without involving the actual database or repository implementation. We will verify different scenarios, such as retrieving, creating, updating, and deleting employees.
What is a unit test?
A unit test is a type of software testing that verifies the functionality of a small, individual unit of code, such as a method or a class, independently of its external dependencies. It helps ensure that each component behaves as expected for different input values and scenarios.
This testing is important because it helps identify bugs early, improves code quality, makes code easier to maintain, and reduces the risk of introducing issues when existing functionality is modified
If you want to learn more about unit testing, its importance, and its benefits, refer to my previous article on Unit Testing.
Understanding xUnit and Moq
xUnit is a popular open-source unit testing framework for .NET applications. It provides the necessary features to write, organize, and execute unit tests, as well as verify the expected results using assertions.
Moq is a mocking library for .NET that helps create mock objects for dependencies, allowing us to test a class independently without relying on actual implementations of its dependencies.
We use both xUnit and Moq because they serve different purposes: xUnit executes the unit tests and verifies the results, while Moq creates mock dependencies and helps verify their interactions.
In our CleanArchitectureDemo project, we will use xUnit to test the EmployeeService and Moq to mock the IEmployeeRepository dependency, ensuring that the service logic can be tested independently without connecting to the actual SQL Server database.
Understanding What We Are Going to Test
In our CleanArchitectureDemo project, we will focus on testing the EmployeeService class, which is part of the Application layer. The EmployeeService contains the application logic for managing employee operations, such as retrieving all employees, retrieving an employee by ID, adding a new employee, updating employee details, and deleting an employee.
This service depends on the IEmployeeRepository interface to perform database operations. Instead of using the actual repository implementation, we will create a mock implementation of IEmployeeRepository using Moq. This allows us to isolate the EmployeeService and test its functionality independently without connecting to the SQL Server database.
- EmployeeService: The actual class whose methods and application logic we want to test.
- IEmployeeRepository: The dependency that will be mocked using Moq.
- Moq: Creates mock repository objects and configures their expected behavior.
- xUnit: Executes the test cases and verifies the expected results.
This approach ensures that our unit tests focus only on the EmployeeService logic rather than testing database connectivity or repository implementation.
Create a Unit Test Project
To write unit tests for our CleanArchitectureDemo application, we first need to create a separate test project. Keeping unit tests in a separate project helps organize the test code independently from the application code and makes it easier to maintain and execute tests.
Step 1: Create CleanArchitectureDemo.Tests project
- Open the existing
CleanArchitectureDemosolution in Visual Studio 2022. - Right-click the solution in Solution Explorer.
- Select Add → New Project.
- Search for xUnit Test Project.
- Select the xUnit Test Project template and click Next.
- Enter
CleanArchitectureDemo.Testsas the project name. - Select the appropriate framework version (for example, .NET 8).
- Click Create.
Visual Studio will create a new test project containing the necessary configuration and a sample unit test class.
Step 2: Add project references
Our unit tests need access to the classes and interfaces defined in the Application layer. Therefore, we need to add a project reference to CleanArchitectureDemo.Application.
- Right-click the
CleanArchitectureDemo.Testsproject. - Select Add → Project Reference.
- Select
CleanArchitectureDemo.Application. - Click OK.
This allows the test project to access classes such as EmployeeService and interfaces such as IEmployeeRepository.
Step 3: Install NuGet packages
Next, we need to install the NuGet packages required for writing and executing unit tests.
Install the following packages:| Package | Purpose |
|---|---|
xunit |
Provides the framework for writing unit tests. |
xunit.runner.visualstudio |
Allows Visual Studio Test Explorer to discover and execute xUnit tests. |
Microsoft.NET.Test.Sdk |
Provides the test execution infrastructure. |
Moq |
Creates mock objects for dependencies such as IEmployeeRepository. |
Right-click the CleanArchitectureDemo.Tests project and select Manage NuGet Packages. Search for and install the packages.
</> powershell
Install-Package xunit
Install-Package xunit.runner.visualstudio
Install-Package Microsoft.NET.Test.Sdk
Install-Package Moq
Make sure you install compatible package versions for your target .NET framework.
Step 4: Verify the project structure
After completing these steps, your solution should have the following structure:</> Plain Text
CleanArchitectureDemo
│
├── CleanArchitectureDemo.Domain
│
├── CleanArchitectureDemo.Application
│ ├── Interfaces
│ └── Services
│ └── EmployeeService.cs
│
├── CleanArchitectureDemo.Infrastructure
│
├── CleanArchitectureDemo.API
│
└── CleanArchitectureDemo.Tests
└── UnitTest1.cs
At this point, our unit test project is ready. We have added the required project reference and installed the NuGet packages needed to write unit tests using xUnit and Moq.
Next, we will create our first unit test for the EmployeeService class and understand how to mock the IEmployeeRepository dependency.
Write Unit Tests for EmployeeService
Now that we have created the CleanArchitectureDemo.Tests project and installed the required NuGet packages, let's write unit tests for the EmployeeService class.
As discussed earlier, the EmployeeService depends on the IEmployeeRepository interface to perform employee-related data operations. Instead of connecting to the actual SQL Server database, we will use Moq to create a mock repository and configure it to return predefined data.
We will use xUnit to write and execute test cases and verify the expected results using assertions.
In this section, we will write unit tests for the following methods:GetAllAsync()– Verify that all employees are retrieved successfully.GetByIdAsync()– Verify that an employee can be retrieved using its ID.AddAsync()– Verify that a new employee is added successfully.UpdateAsync()– Verify that employee details are updated successfully.DeleteAsync()– Verify that an employee is deleted successfully.
Step 1: Create EmployeeServiceTests.cs
Inside the CleanArchitectureDemo.Tests project, rename the default UnitTest1.cs file to EmployeeServiceTests.cs and add the following code..
</> C#
using CleanArchitectureDemo.Application.Interfaces;
using CleanArchitectureDemo.Application.Services;
using CleanArchitectureDemo.Domain.Entities;
using Moq;
using Xunit;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace CleanArchitectureDemo.Tests
{
public class EmployeeServiceTests
{
private readonly Mock<IEmployeeRepository> _mockRepository;
private readonly EmployeeService _employeeService;
public EmployeeServiceTests()
{
_mockRepository = new Mock<IEmployeeRepository>();
_employeeService = new EmployeeService(
_mockRepository.Object);
}
// Unit tests will be added here
}
}
Here:
Mock<IEmployeeRepository>creates a mock repository using Moq._mockRepository.Objectprovides the mock implementation toEmployeeService._employeeServiceis the actual service instance whose methods we want to test.
Step 2: Write a unit test for GetAllAsync()
The GetAllAsync() method retrieves all employees from the repository. We will configure the mock repository to return a predefined list of employees and verify whether the service returns the expected employee records.
EmployeeServiceTests:
</> C#
[Fact]
public async Task GetAllAsync_ShouldReturnAllEmployees()
{
// Arrange
var employees = new List<Employee>();
employees.Add(new Employee(1,"John", "john@example.com", 50000));
employees.Add(new Employee(2,"David", "david@example.com", 60000));
_mockRepository
.Setup(repo => repo.GetAllAsync())
.ReturnsAsync(employees);
// Act
var result = await _employeeService.GetAllAsync();
// Assert
Assert.NotNull(result);
Assert.Equal(2, result.Count());
}
The [Fact] attribute is used here to identify this method as an xUnit test case.
- Arrange: Create sample employee records and configure the mock repository to return them when
GetAllAsync()is called. - Act: Call the actual
EmployeeService.GetAllAsync()method. - Assert: Verify that the returned result is not null and contains two employee records.
Step 3: Write a unit test for GetByIdAsync()
The GetByIdAsync() method retrieves an employee using its ID. We will configure the mock repository to return a specific employee when the requested ID matches.
</> C#
[Fact]
public async Task GetByIdAsync_ShouldReturnEmployee()
{
// Arrange
var employee = new Employee(1,"John", "john@example.com",50000);
_mockRepository
.Setup(repo => repo.GetByIdAsync(1))
.ReturnsAsync(employee);
// Act
var result = await _employeeService.GetByIdAsync(1);
// Assert
Assert.NotNull(result);
Assert.Equal(1, result.Id);
Assert.Equal("John", result.Name);
}
This test verifies that the service correctly retrieves an employee using the supplied ID.
Explanation:- Arrange: Create an employee object and configure the mock repository to return it when
GetByIdAsync(1)is called. - Act: Call
GetByIdAsync(1)on theEmployeeService. - Assert: Verify that the returned employee has the expected ID and name.
Step 4: Write a unit test for AddAsync()
The AddAsync() method adds a new employee. We will verify whether the EmployeeService correctly passes the employee object to the repository's AddAsync() method.
</> C#
[Fact]
public async Task AddAsync_ShouldAddEmployee()
{
// Arrange
var employee = new Employee(1, "John", "john@example.com", 50000);
_mockRepository
.Setup(repo => repo.AddAsync(employee))
.Returns(Task.CompletedTask);
// Act
var employeeDto= new CreateEmployeeDto
{
Name = "John",
Email = "john@example.com",
Salary = 50000
}
await _employeeService.CreateAsync(employeeDto);
// Assert
_mockRepository.Verify(
repo => repo.AddAsync(employee),
Times.Once);
}
Here, Times.Once ensures that the repository operation is invoked only once.
- Arrange: Create a new employee object and configure the mock repository.
- Act: Call
AddAsync()on theEmployeeService. - Assert: Use
Verify()to ensure that the repository'sAddAsync()method was called exactly once with the expected employee object.
Step 5: Write a unit test for UpdateAsync()
The UpdateAsync() method updates an existing employee's details. We will verify whether the service passes the updated employee object to the repository.
</> C#
[Fact]
public async Task UpdateAsync_ShouldUpdateEmployee()
{
// Arrange
var employee = new Employee(1,"John Updated","john.updated@example.com", 70000);
var employeeDto = new UpdateEmployeeDto
{
Name = "John Updated",
Email = "john.updated@example.com",
Salary = 70000
};
_mockRepository
.Setup(repo => repo.UpdateAsync(employee))
.Returns(Task.CompletedTask);
// Act
await _employeeService.UpdateAsync(1, employeeDto);
// Assert
_mockRepository.Verify(
repo => repo.UpdateAsync(employee),
Times.Once);
}
This test ensures that the service correctly delegates the update operation to the repository.
Explanation:- Arrange: Create an employee object containing updated details.
- Act: Call
UpdateAsync()on theEmployeeService. - Assert: Verify that the repository's
UpdateAsync()method was called exactly once with the updated employee object.
Step 6: Write a unit test for DeleteAsync()
The DeleteAsync() method deletes an employee using its ID. We will verify whether the service correctly passes the employee ID to the repository's delete operation.
</> C#
[Fact]
public async Task DeleteAsync_ShouldDeleteEmployee()
{
// Arrange
var employee = new Employee(1, "John", "john@example.com", 50000);
_mockRepository
.Setup(repo => repo.DeleteAsync(employee))
.Returns(Task.CompletedTask);
// Act
await _employeeService.DeleteAsync(employee.Id);
// Assert
_mockRepository.Verify(
repo => repo.DeleteAsync(employee),
Times.Once);
}
This test verifies that the service correctly delegates the delete operation to the repository.
Explanation:- Arrange: Define the employee ID and configure the mock repository.
- Act: Call
DeleteAsync()on theEmployeeService. - Assert: Verify that the repository's
DeleteAsync()method was called exactly once with the specified employee ID.
Understanding the Arrange, Act, and Assert pattern
You may have noticed that all five test methods follow the same structure called the AAA (Arrange, Act, Assert) pattern. This pattern makes unit tests easier to read, understand, and maintain.
| Phase | Purpose |
|---|---|
| Arrange | Prepare test data and configure mock dependencies. |
| Act | Execute the actual method being tested. |
| Assert | Verify that the method produces the expected result or interaction. |
Complete unit test coverage
After adding all five test methods, your EmployeeServiceTests.cs file will contain tests for the following operations:
| Test method | What it verifies |
|---|---|
GetAllAsync_ShouldReturnAllEmployees |
Returns all employee records. |
GetByIdAsync_ShouldReturnEmployee |
Returns the employee for the specified ID. |
AddAsync_ShouldAddEmployee |
Calls the repository to add an employee. |
UpdateAsync_ShouldUpdateEmployee |
Calls the repository to update employee details. |
DeleteAsync_ShouldDeleteEmployee |
Calls the repository to delete an employee. |
At this point, we have written unit tests for all five employee operations. These tests use mocked repository dependencies, so they do not require a SQL Server connection or actual database operations.
Run Unit Tests Using Visual Studio Test Explorer
After writing the unit tests for the EmployeeService class, the next step is to execute them and verify whether they pass successfully. Visual Studio 2022 provides a built-in tool called Test Explorer, which allows us to discover, execute, and monitor unit tests.
Step 1: Open Test Explorer
- Open the
CleanArchitectureDemosolution in Visual Studio 2022. - From the Visual Studio menu, select Test → Test Explorer.
- The Test Explorer window will appear.
Step 2: Build the test project
- In Solution Explorer, right-click the
CleanArchitectureDemo.Testsproject. - Select Build.
- Wait for the build process to complete successfully.
Building the project ensures that the test code compiles and that Visual Studio can discover the available test methods.
Step 3: Run all unit tests
- Open the Test Explorer window.
- Click Run All Tests.
- Visual Studio will discover and execute all the unit tests available in the test project.
- Wait for the execution to complete.
Step 4: Verify the test results
After execution, Test Explorer displays the results of each test case. If all the tests execute successfully, you will see the passed status for all five test methods:
GetAllAsync_ShouldReturnAllEmployeesGetByIdAsync_ShouldReturnEmployeeAddAsync_ShouldAddEmployeeUpdateAsync_ShouldUpdateEmployeeDeleteAsync_ShouldDeleteEmployee
Visual Studio displays passed tests with green check marks. If a test fails, it displays a failed status along with details about the failure.
Step 5: Analyze failed unit tests
If any unit test fails, select the failed test in Test Explorer to view the failure details. The failure information helps identify:
- Which test method failed.
- The expected result.
- The actual result.
- The assertion or exception that caused the failure.
You can use this information to investigate the issue, correct the implementation or test configuration, and execute the tests again.
Summary
In this article, we created a separate CleanArchitectureDemo.Tests project and implemented unit tests for the EmployeeService class using xUnit and Moq. We also learned how to mock the IEmployeeRepository dependency to isolate the service from the actual database and execute the unit tests using Visual Studio 2022 Test Explorer.
By implementing unit testing, we can verify application logic independently, identify issues early, and improve the maintainability and reliability of our Clean Architecture application.
Thanks
