πŸ§ͺ

Test Patterns Guide

Testing Intermediate 1 min read 100 words

Test Patterns and Best Practices

Common patterns for writing maintainable, reliable tests in .NET.

Test Data Builders

Object Mother Pattern

public static class OrderMother
{
    public static Order CreateDefault() => new()
    {
        Id = 1,
        CustomerId = 100,
        OrderDate = DateTime.UtcNow,
        TotalAmount = 99.99m,
        Items = new List<OrderItem>
        {
            new() { ProductId = 1, Quantity = 2, Price = 49.99m }
        }
    };
    
    public static Order CreateWithMultipleItems() => CreateDefault() with
    {
        Items = new List<OrderItem>
        {
            new() { ProductId = 1, Quantity = 1, Price = 29.99m },
            new() { ProductId = 2, Quantity = 2, Price = 19.99m }
        }
    };
}

// Usage
[Test]
public void ProcessOrder_ValidOrder_Success()
{
    var order = OrderMother.CreateDefault();
    var result = _orderService.Process(order);
    Assert.That(result.IsSuccess, Is.True);
}

Test Data Builder Pattern

public class OrderBuilder
{
    private int _id = 1;
    private int _customerId = 100;
    private DateTime _orderDate = DateTime.UtcNow;
    private List<OrderItem> _items = new();
    
    public OrderBuilder WithId(int id)
    {
        _id = id;
        return this;
    }
    
    public OrderBuilder WithCustomer(int customerId)
    {
        _customerId = customerId;
        return this;
    }
    
    public OrderBuilder WithItem(int productId, int quantity, decimal price)
    {
        _items.Add(new OrderItem { ProductId = productId, Quantity = quantity, Price = price });
        return this;
    }
    
    public Order Build() => new()
    {
        Id = _id,
        CustomerId = _customerId,
        OrderDate = _orderDate,
        Items = _items
    };
}

// Usage
[Test]
public void Test()
{
    var order = new OrderBuilder()
        .WithCustomer(200)
        .WithItem(productId: 1, quantity: 2, price: 49.99m)
        .WithItem(productId: 2, quantity: 1, price: 29.99m)
        .Build();
}

AutoFixture

[Test]
public void AutoFixtureExample()
{
    var fixture = new Fixture();
    
    // Generate random data
    var order = fixture.Create<Order>();
    
    // Customize specific properties
    fixture.Customize<Order>(c => c
        .With(o => o.CustomerId, 100)
        .Without(o => o.Items));
    
    var customOrder = fixture.Create<Order>();
}

Test Fixtures

NUnit SetUp/TearDown

[TestFixture]
public class OrderServiceTests
{
    private OrderService _service;
    private Mock<IOrderRepository> _mockRepo;
    
    [SetUp]
    public void SetUp()
    {
        _mockRepo = new Mock<IOrderRepository>();
        _service = new OrderService(_mockRepo.Object);
    }
    
    [TearDown]
    public void TearDown()
    {
        // Cleanup if needed
    }
    
    [Test]
    public void Test1() { }
}

Class Fixtures (xUnit)

public class DatabaseFixture : IDisposable
{
    public ApplicationDbContext DbContext { get; }
    
    public DatabaseFixture()
    {
        var options = new DbContextOptionsBuilder<ApplicationDbContext>()
            .UseInMemoryDatabase("TestDb")
            .Options;
        
        DbContext = new ApplicationDbContext(options);
        DbContext.Database.EnsureCreated();
    }
    
    public void Dispose()
    {
        DbContext.Database.EnsureDeleted();
        DbContext.Dispose();
    }
}

[CollectionDefinition("Database collection")]
public class DatabaseCollection : ICollectionFixture<DatabaseFixture> { }

[Collection("Database collection")]
public class OrderTests
{
    private readonly DatabaseFixture _fixture;
    
    public OrderTests(DatabaseFixture fixture)
    {
        _fixture = fixture;
    }
    
    [Fact]
    public async Task Test()
    {
        await _fixture.DbContext.Orders.AddAsync(new Order());
        await _fixture.DbContext.SaveChangesAsync();
    }
}

Mocking Patterns

Verify Interactions

[Test]
public void SendEmail_ValidOrder_EmailSent()
{
    var mockEmailService = new Mock<IEmailService>();
    var service = new OrderService(mockEmailService.Object);
    
    var order = OrderMother.CreateDefault();
    service.Process(order);
    
    mockEmailService.Verify(
        x => x.SendAsync(It.IsAny<string>(), It.IsAny<string>()),
        Times.Once);
}

Argument Matching

mockEmailService.Verify(
    x => x.SendAsync(
        It.Is<string>(email => email.Contains("@")),
        It.IsAny<string>()),
    Times.Once);

Parameterized Tests

NUnit TestCase

[TestCase(0, ExpectedResult = 0)]
[TestCase(1, ExpectedResult = 1)]
[TestCase(5, ExpectedResult = 120)]
public int Factorial_ValidInput_ReturnsCorrectValue(int n)
{
    return MathHelper.Factorial(n);
}

xUnit Theory

[Theory]
[InlineData(0, 0)]
[InlineData(1, 1)]
[InlineData(5, 120)]
public void Factorial_Theory(int input, int expected)
{
    var result = MathHelper.Factorial(input);
    Assert.Equal(expected, result);
}

Async Testing

[Test]
public async Task ProcessOrderAsync_ValidOrder_CompletesSuccessfully()
{
    var mockRepo = new Mock<IOrderRepository>();
    mockRepo.Setup(x => x.SaveAsync(It.IsAny<Order>()))
        .ReturnsAsync(true);
    
    var service = new OrderService(mockRepo.Object);
    var order = OrderMother.CreateDefault();
    
    var result = await service.ProcessAsync(order);
    
    Assert.That(result.IsSuccess, Is.True);
}

Test Naming Conventions

Method_Scenario_ExpectedResult

[Test]
public void CreateOrder_NullItems_ThrowsArgumentNullException()
{
    Assert.Throws<ArgumentNullException>(() => 
        _service.CreateOrder(null));
}

[Test]
public void CalculateTotal_MultipleItems_ReturnsSumOfPrices()
{
    var order = new OrderBuilder()
        .WithItem(1, 1, 10m)
        .WithItem(2, 2, 20m)
        .Build();
    
    var total = _service.CalculateTotal(order);
    
    Assert.That(total, Is.EqualTo(50m));
}

AAA Pattern

[Test]
public void Example()
{
    // Arrange
    var order = OrderMother.CreateDefault();
    var service = new OrderService();
    
    // Act
    var result = service.Process(order);
    
    // Assert
    Assert.That(result.IsSuccess, Is.True);
}

Related Topics

πŸ“š Related Articles