Testing Standards for .NET
Unit Testing Requirements
-
Mandatory: Unit tests are required for all business logic, commands, and handlers.
-
Organization Standard: Follow the AAA (Arrange-Act-Assert) principle.
-
Separation of Concerns: Create builder classes to generate all variables, objects, and data needed for unit tests. This keeps tests clean and maintainable.
-
Coverage: Minimum expected code coverage is 80%.
Unit Test Example Structure
public class CreateUserCommandHandlerTests { [Fact] public async Task Handle_ValidCommand_ShouldCreateUser() { // Arrange var builder = new UserTestBuilder(); var user = builder.WithEmail("test@example.com").Build(); var command = new CreateUserCommand { Email = user.Email };
// Act
var result = await _handler.Handle(command, CancellationToken.None);
// Assert
result.Should().NotBeNull();
result.Email.Should().Be(user.Email);
}
}
Integration Testing Standards (Agaval Standard)
-
Required: Integration tests are required to verify API endpoint functionality and cross-layer interactions.
-
Persistence Isolation: Priority is to use in-memory databases (e.g., EF Core In-Memory) for integration tests. NEVER use development/production environment databases.
-
API and Startup: API orchestration and startup must always be done using WebApplicationFactory .
-
External Service Simulation: Simulate external services (APIs, Brokers, etc.) using WireMockServer or similar mock objects to ensure test isolation.
-
Separation of Concerns: Create a class called WebApplicationTestFactory and establish all the previously mentioned configurations and necessary settings for tests there.
Integration Test Example Structure
public class WebApplicationTestFactory : WebApplicationFactory<Program> { protected override void ConfigureWebHost(IWebHostBuilder builder) { builder.ConfigureServices(services => { // Remove the real database context var descriptor = services.SingleOrDefault( d => d.ServiceType == typeof(DbContextOptions<AppDbContext>)); if (descriptor != null) services.Remove(descriptor);
// Add in-memory database
services.AddDbContext<AppDbContext>(options =>
{
options.UseInMemoryDatabase("TestDb");
});
// Replace external services with mocks
services.AddSingleton<IExternalService, MockExternalService>();
});
}
}
public class UserEndpointTests : IClassFixture<WebApplicationTestFactory> { private readonly HttpClient _client;
public UserEndpointTests(WebApplicationTestFactory factory)
{
_client = factory.CreateClient();
}
[Fact]
public async Task GetUsers_ShouldReturnOk()
{
// Arrange & Act
var response = await _client.GetAsync("/api/users");
// Assert
response.StatusCode.Should().Be(HttpStatusCode.OK);
}
}
Test Builder Pattern
Create dedicated builder classes for complex test data:
public class UserTestBuilder { private string _email = "default@test.com"; private string _name = "Test User";
public UserTestBuilder WithEmail(string email)
{
_email = email;
return this;
}
public UserTestBuilder WithName(string name)
{
_name = name;
return this;
}
public User Build()
{
return new User
{
Email = _email,
Name = _name
};
}
}
Best Practices
-
Keep tests independent and isolated.
-
Use meaningful test names that describe the scenario.
-
Mock external dependencies properly.
-
Clean up test data after each test run.
-
Use assertion libraries like FluentAssertions for readable assertions.