testing-patterns

Joshua-Palamuttam's avatarfrom Joshua-Palamuttam

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.

⭐0stars🔀0forks📁View on GitHub🕐Updated Dec 8, 2025

When & Why to Use This Skill

This Claude skill provides a comprehensive framework for implementing professional unit testing patterns using xUnit, Moq, and FluentAssertions. It streamlines the creation of maintainable and readable test suites by enforcing the Arrange-Act-Assert (AAA) pattern, standardized naming conventions, and robust mocking strategies. Designed to enhance software reliability, it ensures high-quality code through systematic edge-case coverage and clear assertion logic.

Use Cases

  • Standardizing unit test structures across a development team using the Arrange-Act-Assert (AAA) pattern for better maintainability.
  • Implementing descriptive naming conventions (Method_Scenario_Expected) to make test suites self-documenting and easier to debug.
  • Configuring complex mock behaviors and verifying dependency interactions using Moq to isolate the System Under Test (SUT).
  • Writing expressive and human-readable assertions with FluentAssertions to improve the clarity of test failure messages.
  • Ensuring comprehensive test coverage for critical edge cases such as null returns, empty collections, and input validation failures.
nametesting-patterns
descriptionUnit 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()
{
    // 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

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

// 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

// 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

_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