# 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(); var order = await repository.GetByIdAsync(orderId); // returns null! order.Status; // NullReferenceException // ✅ GOOD: Configure mock to return data var repository = Substitute.For(); 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(); var unitOfWork = Substitute.For(); // Important: Link repository to unitOfWork repository.UnitOfWork.Returns(unitOfWork); // Configure SaveChangesAsync behavior unitOfWork.SaveChangesAsync(Arg.Any()) .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()); ``` --- ## 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 { 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 _options; private readonly OrderContext _context; public OrderRepositoryTests() { _options = new DbContextOptionsBuilder() .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 { protected override void ConfigureWebHost(IWebHostBuilder builder) { builder.ConfigureServices(services => { // Remove real auth services.RemoveAll(); // Add test auth services.AddAuthentication("Test") .AddScheme( "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() .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(); 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)