| name | testing-patterns |
| description | Unit testing patterns with xUnit, Moq, and FluentAssertions including AAA pattern, naming conventions, mocking, and edge case coverage. Use when writing or reviewing unit tests. |
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:
[Fact]
public async Task GetByIdAsync_WithValidId_ReturnsTask()
{
var taskId = Guid.NewGuid();
var expectedTask = new TaskItem { Id = taskId, Title = "Test Task" };
_repositoryMock.Setup(r => r.GetByIdAsync(taskId, It.IsAny<CancellationToken>()))
.ReturnsAsync(expectedTask);
var result = await _sut.GetByIdAsync(taskId);
result.Should().NotBeNull();
result!.Id.Should().Be(taskId);
result.Title.Should().Be("Test Task");
}
Test Class Structure
public class TaskServiceTests
{
private readonly Mock<ITaskRepository> _repositoryMock;
private readonly Mock<ILogger<TaskService>> _loggerMock;
private readonly TaskService _sut;
public TaskServiceTests()
{
_repositoryMock = new Mock<ITaskRepository>();
_loggerMock = new Mock<ILogger<TaskService>>();
_sut = new TaskService(_repositoryMock.Object, _loggerMock.Object);
}
}
Naming Conventions
Test Method Names
Format: MethodName_Scenario_ExpectedResult
public async Task GetByIdAsync_WithValidId_ReturnsTask()
public async Task GetByIdAsync_WithNonExistentId_ReturnsNull()
public async Task CreateAsync_WithValidRequest_CreatesAndReturnsTask()
public async Task DeleteAsync_WithExistingId_ReturnsTrue()
public async Task TestGet()
public async Task Test1()
Mocking with Moq
Setup Mock Returns
_repositoryMock.Setup(r => r.GetByIdAsync(taskId, It.IsAny<CancellationToken>()))
.ReturnsAsync(expectedTask);
_repositoryMock.Setup(r => r.GetByIdAsync(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((TaskItem?)null);
Verify Mock Calls
_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
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