Testing Patterns
Overview
Tests ensure code correctness and enable confident refactoring. Follow these patterns for consistent, maintainable tests.
Test Framework Stack
- •xUnit - Test framework
- •Moq - Mocking library
- •FluentAssertions - Readable assertions
Test Structure
Arrange-Act-Assert (AAA)
Every test follows this pattern:
csharp
[Fact]
public async Task GetByIdAsync_WithValidId_ReturnsTask()
{
// Arrange
var taskId = Guid.NewGuid();
var expectedTask = new TaskItem { Id = taskId, Title = "Test Task" };
_repositoryMock.Setup(r => r.GetByIdAsync(taskId, It.IsAny<CancellationToken>()))
.ReturnsAsync(expectedTask);
// Act
var result = await _sut.GetByIdAsync(taskId);
// Assert
result.Should().NotBeNull();
result!.Id.Should().Be(taskId);
result.Title.Should().Be("Test Task");
}
Test Class Structure
csharp
public class TaskServiceTests
{
private readonly Mock<ITaskRepository> _repositoryMock;
private readonly Mock<ILogger<TaskService>> _loggerMock;
private readonly TaskService _sut; // System Under Test
public TaskServiceTests()
{
_repositoryMock = new Mock<ITaskRepository>();
_loggerMock = new Mock<ILogger<TaskService>>();
_sut = new TaskService(_repositoryMock.Object, _loggerMock.Object);
}
// Tests...
}
Naming Conventions
Test Method Names
Format: MethodName_Scenario_ExpectedResult
csharp
// Good - descriptive names public async Task GetByIdAsync_WithValidId_ReturnsTask() public async Task GetByIdAsync_WithNonExistentId_ReturnsNull() public async Task CreateAsync_WithValidRequest_CreatesAndReturnsTask() public async Task DeleteAsync_WithExistingId_ReturnsTrue() // Bad - unclear names public async Task TestGet() public async Task Test1()
Mocking with Moq
Setup Mock Returns
csharp
// Return a value
_repositoryMock.Setup(r => r.GetByIdAsync(taskId, It.IsAny<CancellationToken>()))
.ReturnsAsync(expectedTask);
// Return null
_repositoryMock.Setup(r => r.GetByIdAsync(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((TaskItem?)null);
Verify Mock Calls
csharp
_repositoryMock.Verify(r => r.CreateAsync(It.IsAny<TaskItem>(), It.IsAny<CancellationToken>()), Times.Once); _repositoryMock.Verify(r => r.DeleteAsync(taskId, It.IsAny<CancellationToken>()), Times.Once);
FluentAssertions
Basic Assertions
csharp
result.Should().Be(expected); result.Should().BeNull(); result.Should().NotBeNull(); items.Should().HaveCount(3); items.Should().BeEmpty(); result.IsCompleted.Should().BeTrue();
Edge Case Coverage
Always Test These Scenarios
- •GET: found, not found, empty list
- •CREATE: success, validates input, sets defaults
- •UPDATE: success, not found, partial update
- •DELETE: success, not found