---
description: C# Testing
alwaysApply: false
---

# C# Testing

Test behavior, not implementation. Every test answers: "what does this code do?"

## Framework Stack

xUnit (test framework), NSubstitute (mocking), FluentAssertions (readable assertions), Bogus (test data), Testcontainers (real DBs), ArchUnitNET (architecture tests)

## Unit Test Structure

```csharp
[Fact]
public async Task CreateOrder_WithValidItems_ReturnsOrder() {
    // Arrange
    var request = new CreateOrderRequest("cust-1", [new("sku-1", 2)]);
    _inventory.CheckAvailabilityAsync("sku-1", 2, Arg.Any<CancellationToken>())
        .Returns(true);
    // Act
    var result = await _sut.CreateAsync(request, CancellationToken.None);
    // Assert
    result.IsSuccess.Should().BeTrue();
    result.Value!.CustomerId.Should().Be("cust-1");
}
```

## Naming: `Method_Scenario_ExpectedBehavior`

```csharp
[Fact] public async Task GetByEmail_WhenNotFound_ReturnsNull() { }
[Fact] public async Task Register_WithDuplicateEmail_ReturnsConflict() { }
```

## Parameterized Tests

```csharp
[Theory]
[InlineData("", false)]
[InlineData("abc", true)]
[InlineData("valid@email.com", true)]
public void Validate_Password_MinLength(string input, bool expected) {
    PasswordValidator.IsValid(input).Should().Be(expected);
}
```

## Integration Tests

```csharp
public class OrderTests : IClassFixture<WebApplicationFactory<Program>> {
    [Fact]
    public async Task POST_orders_returns_created() {
        var response = await _client.PostAsJsonAsync("/orders", request);
        response.StatusCode.Should().Be(HttpStatusCode.Created);
    }
}
// Use Testcontainers for real database integration tests
```

## Architecture Tests

```csharp
[Fact]
public void Domain_should_not_depend_on_infrastructure() {
    Types().That().ResideInNamespace("MyApp.Domain")
        .Should().NotDependOnAny("MyApp.Infrastructure")
        .Check(Architecture);
}
```

## Anti-Patterns

- **Testing implementation details** — verify outcomes, not method call counts
- **Shared mutable state** — fresh state in constructor, never `static` collections
- **`Thread.Sleep` in tests** — use polling with timeout or proper async sync
