Isaac.

testing

Integration Testing with ASP.NET Core

Write comprehensive integration tests for real-world scenarios.

By Emem IsaacJuly 26, 20233 min read
#testing#integration tests#xunit#aspnet core
Share:

A Simple Analogy

Integration tests are like rehearsals for a play. You run the full system, including real databases and services, to ensure everything works together before opening night.


Why Integration Tests?

  • Real scenarios: Test actual dependencies
  • Catch issues: Mocking hides integration problems
  • Confidence: Verify complete workflows
  • Regression: Prevent breaking changes
  • Documentation: Tests show usage patterns

WebApplicationFactory

public class ApiWebApplicationFactory : WebApplicationFactory<Program>
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureServices(services =>
        {
            // Replace real dependencies with test doubles
            var descriptor = services.FirstOrDefault(
                d => d.ServiceType == typeof(IOrderService));
            
            if (descriptor != null)
                services.Remove(descriptor);
            
            services.AddScoped<IOrderService, TestOrderService>();
        });
    }
}

public class OrderControllerTests : IClassFixture<ApiWebApplicationFactory>
{
    private readonly HttpClient _client;
    
    public OrderControllerTests(ApiWebApplicationFactory factory)
    {
        _client = factory.CreateClient();
    }
    
    [Fact]
    public async Task GetOrder_WithValidId_ReturnsOk()
    {
        // Arrange
        var orderId = "order-123";
        
        // Act
        var response = await _client.GetAsync($"/api/orders/{orderId}");
        
        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.OK);
        var content = await response.Content.ReadAsStringAsync();
        var order = JsonSerializer.Deserialize<OrderDto>(content);
        order.Should().NotBeNull();
    }
}

In-Memory Database Testing

public class OrderRepositoryTests
{
    private AppDbContext GetDbContext()
    {
        var options = new DbContextOptionsBuilder<AppDbContext>()
            .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
            .Options;
        
        return new AppDbContext(options);
    }
    
    [Fact]
    public async Task SaveOrder_WithValidOrder_Succeeds()
    {
        // Arrange
        var context = GetDbContext();
        var repository = new OrderRepository(context);
        var order = new Order { Id = 1, CustomerId = "cust-1", Total = 100m };
        
        // Act
        await repository.SaveAsync(order);
        await context.SaveChangesAsync();
        
        // Assert
        var saved = await context.Orders.FindAsync(1);
        saved.Should().NotBeNull();
        saved.Total.Should().Be(100m);
    }
}

Container-Based Tests

public class PostgresIntegrationTests : IAsyncLifetime
{
    private readonly PostgresContainer _postgres = new PostgresBuilder()
        .WithImage("postgres:15")
        .WithDatabase("testdb")
        .WithUsername("test")
        .WithPassword("test")
        .Build();
    
    public async Task InitializeAsync()
    {
        await _postgres.StartAsync();
    }
    
    public async Task DisposeAsync()
    {
        await _postgres.StopAsync();
    }
    
    [Fact]
    public async Task SaveOrder_ToRealDatabase_Succeeds()
    {
        // Connect to real container
        var connection = _postgres.GetConnectionString();
        var options = new DbContextOptionsBuilder<AppDbContext>()
            .UseNpgsql(connection)
            .Options;
        
        using var context = new AppDbContext(options);
        await context.Database.MigrateAsync();
        
        // Test with real database
        var order = new Order { CustomerId = "cust-1", Total = 100m };
        context.Orders.Add(order);
        await context.SaveChangesAsync();
        
        var saved = await context.Orders.FindAsync(order.Id);
        saved.Should().NotBeNull();
    }
}

API Integration Tests

public class OrderApiTests : IClassFixture<ApiWebApplicationFactory>
{
    private readonly HttpClient _client;
    
    public OrderApiTests(ApiWebApplicationFactory factory)
    {
        _client = factory.CreateClient();
    }
    
    [Fact]
    public async Task CreateOrder_WithValidPayload_Returns201()
    {
        // Arrange
        var request = new CreateOrderRequest
        {
            CustomerId = "cust-123",
            Items = new List<OrderItem>
            {
                new OrderItem { ProductId = "p1", Quantity = 2, Price = 29.99m }
            }
        };
        
        var json = JsonSerializer.Serialize(request);
        var content = new StringContent(json, Encoding.UTF8, "application/json");
        
        // Act
        var response = await _client.PostAsync("/api/orders", content);
        
        // Assert
        response.StatusCode.Should().Be(HttpStatusCode.Created);
        var responseContent = await response.Content.ReadAsStringAsync();
        var dto = JsonSerializer.Deserialize<OrderDto>(responseContent);
        dto.Id.Should().NotBeEmpty();
    }
    
    [Fact]
    public async Task GetOrder_AfterCreate_ReturnsCreatedOrder()
    {
        // Create first
        var createResponse = await _client.PostAsync("/api/orders", ...);
        var created = await createResponse.Content.ReadAsAsync<OrderDto>();
        
        // Then get
        var getResponse = await _client.GetAsync($"/api/orders/{created.Id}");
        var retrieved = await getResponse.Content.ReadAsAsync<OrderDto>();
        
        retrieved.Id.Should().Be(created.Id);
    }
}

Best Practices

  1. Test full workflows: Create, read, update, delete
  2. Use real dependencies: When possible
  3. Clean up: Reset state between tests
  4. Avoid interdependence: Tests should run independently
  5. Test error paths: Not just happy paths

Related Concepts

  • Unit testing mocks and stubs
  • Performance testing
  • End-to-end testing
  • Test automation best practices

Summary

Integration tests validate complete system behavior by testing real dependencies. Use WebApplicationFactory and in-memory databases for fast, reliable integration tests.

Share:

Written by Emem Isaac

Expert Software Engineer with 15+ years of experience building scalable enterprise applications. Specialized in ASP.NET Core, Azure, Docker, and modern web development. Passionate about sharing knowledge and helping developers grow.

Ready to Build Something Amazing?

Let's discuss your project and explore how my expertise can help you achieve your goals. Free consultation available.

💼 Trusted by 50+ companies worldwide | ⚡ Average response time: 24 hours