Integration Testing

লেসন ৪২ / ৫০

Integration Test কী?

Real HTTP request → Controller → Service → DB — পুরো pipeline test। Bug catch করে যা unit test missed।

Setup

dotnet add package Microsoft.AspNetCore.Mvc.Testing
dotnet add package Microsoft.EntityFrameworkCore.InMemory

WebApplicationFactory

public class CustomWebAppFactory : WebApplicationFactory<Program> {
    protected override void ConfigureWebHost(IWebHostBuilder builder) {
        builder.ConfigureServices(services => {
            // Real DbContext remove
            var desc = services.SingleOrDefault(d =>
                d.ServiceType == typeof(DbContextOptions<AppDbContext>));
            if (desc != null) services.Remove(desc);

            // In-memory DB add
            services.AddDbContext<AppDbContext>(opt =>
                opt.UseInMemoryDatabase("IntegrationTestDb"));

            // Seed data
            using var scope = services.BuildServiceProvider().CreateScope();
            var ctx = scope.ServiceProvider.GetRequiredService<AppDbContext>();
            ctx.Database.EnsureCreated();
            ctx.Products.Add(new Product { Id = 1, Name = "Test", Price = 100 });
            ctx.SaveChanges();
        });
    }
}

API Test

public class ProductsApiTests : IClassFixture<CustomWebAppFactory> {
    private readonly HttpClient _client;
    public ProductsApiTests(CustomWebAppFactory factory) {
        _client = factory.CreateClient();
    }

    [Fact]
    public async Task GetAll_Returns200() {
        var response = await _client.GetAsync("/api/products");
        response.EnsureSuccessStatusCode();
        var json = await response.Content.ReadAsStringAsync();
        var products = JsonSerializer.Deserialize<List<ProductDto>>(json,
            new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
        products.Should().NotBeEmpty();
    }

    [Fact]
    public async Task Post_CreatesProduct() {
        var dto = new { Name = "New", Price = 500 };
        var response = await _client.PostAsJsonAsync("/api/products", dto);
        response.StatusCode.Should().Be(HttpStatusCode.Created);
    }

    [Fact]
    public async Task Get_NotExists_Returns404() {
        var response = await _client.GetAsync("/api/products/9999");
        response.StatusCode.Should().Be(HttpStatusCode.NotFound);
    }
}

Authenticated Test

// Custom AuthHandler দিয়ে fake auth
public class TestAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions> {
    protected override Task<AuthenticateResult> HandleAuthenticateAsync() {
        var claims = new[] { new Claim(ClaimTypes.Name, "TestUser") };
        var identity = new ClaimsIdentity(claims, "Test");
        var principal = new ClaimsPrincipal(identity);
        var ticket = new AuthenticationTicket(principal, "Test");
        return Task.FromResult(AuthenticateResult.Success(ticket));
    }
}

// Factory ConfigureServices-এ
services.AddAuthentication("Test")
    .AddScheme<AuthenticationSchemeOptions, TestAuthHandler>("Test", null);

// Test
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Test");

Testcontainers (Real DB in Docker)

dotnet add package Testcontainers.MsSql

var container = new MsSqlBuilder().WithPassword("Yourpass!").Build();
await container.StartAsync();
var connStr = container.GetConnectionString();
// real SQL Server in container — production-like test