# Test Failures Guide

Common test failure patterns and how to fix them in .NET microservices.

## Mock Setup Issues

### NullReferenceException in tests

**Symptom:**
```
System.NullReferenceException: Object reference not set to an instance of an object
```

**Common Cause:** Mock not properly configured

**Solution:**
```csharp
// ❌ BAD: Mock returns null by default
var repository = Substitute.For<IOrderRepository>();
var order = await repository.GetByIdAsync(orderId); // returns null!
order.Status; // NullReferenceException

// ✅ GOOD: Configure mock to return data
var repository = Substitute.For<IOrderRepository>();
var expectedOrder = new Order(/* ... */);
repository.GetByIdAsync(orderId).Returns(expectedOrder);

var order = await repository.GetByIdAsync(orderId);
Assert.NotNull(order);
```

---

### UnitOfWork mock setup

**Problem:** `SaveChangesAsync` not properly mocked

**Solution:**
```csharp
// ✅ Complete mock setup
var repository = Substitute.For<IOrderRepository>();
var unitOfWork = Substitute.For<IUnitOfWork>();

// Important: Link repository to unitOfWork
repository.UnitOfWork.Returns(unitOfWork);

// Configure SaveChangesAsync behavior
unitOfWork.SaveChangesAsync(Arg.Any<CancellationToken>())
    .Returns(Task.FromResult(1));

// Now handler can use it
var handler = new CreateOrderHandler(repository);
await handler.Handle(command, CancellationToken.None);

// Verify SaveChangesAsync was called
await unitOfWork.Received(1).SaveChangesAsync(Arg.Any<CancellationToken>());
```

---

## Async/Await Issues

### Test hangs or times out

**Common Causes:**
1. Missing `await`
2. Deadlock from `.Result` or `.Wait()`
3. Infinite loop

**Solutions:**
```csharp
// ❌ BAD: Forgetting await
[Fact]
public async Task TestMethod()
{
    var result = handler.Handle(command, ct); // Missing await!
    Assert.NotNull(result); // Wrong - this is Task, not result
}

// ✅ GOOD: Proper await
[Fact]
public async Task TestMethod()
{
    var result = await handler.Handle(command, ct);
    Assert.NotNull(result);
}

// ❌ BAD: Blocking async code
var result = repository.GetByIdAsync(id).Result; // Deadlock risk!

// ✅ GOOD: Await properly
var result = await repository.GetByIdAsync(id);
```

---

## Assertion Failures

### Expected vs Actual mismatch

**Symptom:**
```
Assert.Equal() Failure
Expected: 5
Actual:   0
```

**Debugging:**
```csharp
// ❌ Unclear what went wrong
Assert.Equal(5, order.Items.Count);

// ✅ Better: Add message
Assert.Equal(5, order.Items.Count, 
    $"Expected 5 items but got {order.Items.Count}");

// ✅ Best: Use specific assertions
Assert.NotEmpty(order.Items);
Assert.Equal(5, order.Items.Count);
Assert.All(order.Items, item => Assert.NotNull(item.ProductId));
```

---

### Collection comparison failures

**Problem:** Comparing collections incorrectly

**Solutions:**
```csharp
// ❌ BAD: Reference comparison
var expected = new List<int> { 1, 2, 3 };
var actual = service.GetNumbers();
Assert.Equal(expected, actual); // May fail even if content same

// ✅ GOOD: Value comparison
Assert.Equal(expected.Count, actual.Count);
Assert.All(expected, item => Assert.Contains(item, actual));

// ✅ Or use FluentAssertions
actual.Should().BeEquivalentTo(expected);
```

---

## Database Test Issues

### Test fails due to database state

**Problem:** Tests interfere with each other

**Solutions:**
```csharp
// ✅ Use in-memory database per test
public class OrderRepositoryTests : IDisposable
{
    private readonly DbContextOptions<OrderContext> _options;
    private readonly OrderContext _context;
    
    public OrderRepositoryTests()
    {
        _options = new DbContextOptionsBuilder<OrderContext>()
            .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
            .Options;
        
        _context = new OrderContext(_options);
    }
    
    public void Dispose()
    {
        _context.Database.EnsureDeleted();
        _context.Dispose();
    }
}

// ✅ Or use Testcontainers for real database
public class OrderRepositoryIntegrationTests : IAsyncLifetime
{
    private PostgreSqlContainer _container;
    
    public async Task InitializeAsync()
    {
        _container = new PostgreSqlBuilder().Build();
        await _container.StartAsync();
    }
    
    public async Task DisposeAsync()
    {
        await _container.DisposeAsync();
    }
}
```

---

## Integration Test Issues

### TestServer authentication failures

**Problem:** Requests return 401 Unauthorized

**Solutions:**
```csharp
// ✅ Setup test authentication
public class CustomWebApplicationFactory : WebApplicationFactory<Program>
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureServices(services =>
        {
            // Remove real auth
            services.RemoveAll<IAuthenticationService>();
            
            // Add test auth
            services.AddAuthentication("Test")
                .AddScheme<AuthenticationSchemeOptions, TestAuthHandler>(
                    "Test", options => { });
        });
    }
}

// Usage in test
var client = _factory.CreateClient();
client.DefaultRequestHeaders.Add("X-Test-User-Id", userId.ToString());
```

---

### Port already in use

**Error:**
```
Address already in use: bind
```

**Solutions:**
```csharp
// ✅ Let TestServer choose random port
var factory = new WebApplicationFactory<Program>()
    .WithWebHostBuilder(builder =>
    {
        builder.UseUrls(); // Empty = random port
    });

// ✅ Or use different port per test class
builder.UseUrls("http://localhost:0"); // 0 = random port
```

---

## Flaky Tests

### Tests pass/fail intermittently

**Common Causes:**
1. Race conditions
2. Time-dependent logic
3. Shared state
4. External dependencies

**Solutions:**
```csharp
// ❌ BAD: Time-dependent test
[Fact]
public void TestCreatedDate()
{
    var order = new Order();
    Assert.Equal(DateTime.UtcNow, order.CreatedAt); // Flaky!
}

// ✅ GOOD: Test with tolerance
[Fact]
public void TestCreatedDate()
{
    var before = DateTime.UtcNow;
    var order = new Order();
    var after = DateTime.UtcNow;
    
    Assert.InRange(order.CreatedAt, before, after);
}

// ✅ Or inject time provider
public class Order
{
    public DateTime CreatedAt { get; }
    
    public Order(ITimeProvider timeProvider)
    {
        CreatedAt = timeProvider.UtcNow;
    }
}

// In test: mock time
var timeProvider = Substitute.For<ITimeProvider>();
timeProvider.UtcNow.Returns(new DateTime(2024, 1, 1));
```

---

## Test Coverage Issues

### Low coverage on critical code

**Problem:** Important logic not tested

**Solutions:**
```bash
# Generate coverage report
dotnet test /p:CollectCoverage=true /p:CoverageReportFormat=cobertura

# View HTML report
reportgenerator -reports:coverage.cobertura.xml -targetdir:coveragereport
open coveragereport/index.html

# Or use coverlet
dotnet add package coverlet.collector
dotnet test --collect:"XPlat Code Coverage"
```

**Focus on:**
- Domain logic (business rules)
- Command/Query handlers
- Critical paths (checkout, payment, etc.)

---

## Quick Troubleshooting Checklist

When tests fail:

- [ ] Check mock setup (returns correct values?)
- [ ] Verify async/await (no missing `await`?)
- [ ] Check assertions (expected vs actual clear?)
- [ ] Isolate test (run alone, not in suite)
- [ ] Check test output logs
- [ ] Add debug logging
- [ ] Use debugger with breakpoints

For integration tests:

- [ ] Check TestServer configuration
- [ ] Verify authentication setup
- [ ] Check database state (clean between tests?)
- [ ] Review test ordering (independent?)
- [ ] Check external dependencies (mocked?)

---

## Related Resources

- [Build Errors Catalog](build-errors.md)
- [Debugging Guide](debugging-guide.md)
- [dotnet-senior-tester skill](../../dotnet-senior-tester/SKILL.md)
- Main Skill: [development-lifecycle](../SKILL.md)
