Ana içeriğe geç

Integration Testing

Integration testler, birden fazla bileşenin birlikte doğru çalıştığını doğrular; yanlış kurulum yavaş, güvenilmez ve bakımı zor testlere yol açar.


1. Gerçek Veritabanı Kullanmak

Yanlış Kullanım: Paylaşılan veritabanına bağlı test yazmak.

public class OrderTests
{
    [Fact]
    public async Task CreateOrder_SavesInDatabase()
    {
        var options = new DbContextOptionsBuilder<AppDbContext>()
            .UseSqlServer("Server=localhost;Database=TestDb;...") // Gerçek DB
            .Options;

        using var context = new AppDbContext(options);
        // Test verileri diğer testlerle çakışabilir
    }
}

İdeal Kullanım: InMemory veya container-based veritabanı kullanın.

public class OrderTests
{
    [Fact]
    public async Task CreateOrder_SavesInDatabase()
    {
        var options = new DbContextOptionsBuilder<AppDbContext>()
            .UseInMemoryDatabase(Guid.NewGuid().ToString()) // Her test izole
            .Options;

        using var context = new AppDbContext(options);
        var service = new OrderService(context);

        var order = await service.CreateAsync(new CreateOrderDto { CustomerId = 1 });

        Assert.NotEqual(0, order.Id);
        Assert.Equal(1, await context.Orders.CountAsync());
    }
}

2. WebApplicationFactory Kullanmamak

Yanlış Kullanım: API testleri için gerçek sunucu başlatmak.

[Fact]
public async Task GetProducts_ReturnsOk()
{
    using var client = new HttpClient();
    client.BaseAddress = new Uri("https://localhost:5001"); // Sunucu çalışıyor olmalı

    var response = await client.GetAsync("/api/products");
    Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}

İdeal Kullanım: WebApplicationFactory ile in-memory test sunucusu oluşturun.

public class ApiTests : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly HttpClient _client;

    public ApiTests(WebApplicationFactory<Program> factory)
    {
        _client = factory.WithWebHostBuilder(builder =>
        {
            builder.ConfigureServices(services =>
            {
                services.RemoveAll<AppDbContext>();
                services.AddDbContext<AppDbContext>(options =>
                    options.UseInMemoryDatabase("TestDb"));
            });
        }).CreateClient();
    }

    [Fact]
    public async Task GetProducts_ReturnsOkWithProducts()
    {
        var response = await _client.GetAsync("/api/products");

        response.EnsureSuccessStatusCode();
        var products = await response.Content.ReadFromJsonAsync<List<ProductDto>>();
        Assert.NotNull(products);
    }
}

3. Test Verisi Temizliğini Yapmamak

Yanlış Kullanım: Testler arasında veri kalıntısı bırakmak.

public class ProductTests
{
    private static readonly AppDbContext _context = CreateContext();

    [Fact]
    public async Task Test1_CreateProduct()
    {
        _context.Products.Add(new Product { Name = "Test" });
        await _context.SaveChangesAsync(); // Bu veri Test2'yi etkiler
    }

    [Fact]
    public async Task Test2_GetAllProducts()
    {
        var count = await _context.Products.CountAsync();
        Assert.Equal(0, count); // Başarısız! Test1'in verisi kaldı
    }
}

İdeal Kullanım: Her test için temiz veritabanı veya transaction rollback kullanın.

public class ProductTests : IDisposable
{
    private readonly AppDbContext _context;

    public ProductTests()
    {
        var options = new DbContextOptionsBuilder<AppDbContext>()
            .UseInMemoryDatabase(Guid.NewGuid().ToString())
            .Options;
        _context = new AppDbContext(options);
    }

    [Fact]
    public async Task CreateProduct_IncreasesCount()
    {
        _context.Products.Add(new Product { Name = "Test" });
        await _context.SaveChangesAsync();

        Assert.Equal(1, await _context.Products.CountAsync());
    }

    public void Dispose() => _context.Dispose();
}

4. Custom WebApplicationFactory Oluşturmamak

Yanlış Kullanım: Her test sınıfında aynı konfigürasyonu tekrarlamak.

public class OrderApiTests : IClassFixture<WebApplicationFactory<Program>>
{
    public OrderApiTests(WebApplicationFactory<Program> factory)
    {
        _client = factory.WithWebHostBuilder(b =>
        {
            b.ConfigureServices(s => { /* 20 satır setup */ });
        }).CreateClient();
    }
}

public class ProductApiTests : IClassFixture<WebApplicationFactory<Program>>
{
    public ProductApiTests(WebApplicationFactory<Program> factory)
    {
        _client = factory.WithWebHostBuilder(b =>
        {
            b.ConfigureServices(s => { /* Aynı 20 satır setup */ });
        }).CreateClient();
    }
}

İdeal Kullanım: Paylaşılan custom factory oluşturun.

public class CustomWebApplicationFactory : WebApplicationFactory<Program>
{
    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureServices(services =>
        {
            services.RemoveAll<AppDbContext>();
            services.AddDbContext<AppDbContext>(options =>
                options.UseInMemoryDatabase("IntegrationTests"));

            services.RemoveAll<IEmailService>();
            services.AddSingleton<IEmailService, FakeEmailService>();
        });

        builder.UseEnvironment("Testing");
    }
}

public class OrderApiTests : IClassFixture<CustomWebApplicationFactory>
{
    private readonly HttpClient _client;

    public OrderApiTests(CustomWebApplicationFactory factory)
    {
        _client = factory.CreateClient();
    }

    [Fact]
    public async Task CreateOrder_ReturnsCreated()
    {
        var response = await _client.PostAsJsonAsync("/api/orders",
            new { CustomerId = 1, ProductId = 1 });

        Assert.Equal(HttpStatusCode.Created, response.StatusCode);
    }
}

5. Testcontainers Kullanmamak

Yanlış Kullanım: Integration testlerde InMemory provider ile gerçek DB davranışını simüle etmeye çalışmak.

services.AddDbContext<AppDbContext>(options =>
    options.UseInMemoryDatabase("TestDb"));
// InMemory, foreign key, transaction, raw SQL desteklemez

İdeal Kullanım: Testcontainers ile gerçek veritabanı container’ı kullanın.

public class DatabaseFixture : IAsyncLifetime
{
    private readonly PostgreSqlContainer _container = new PostgreSqlBuilder()
        .WithImage("postgres:16-alpine")
        .Build();

    public string ConnectionString => _container.GetConnectionString();

    public async Task InitializeAsync() => await _container.StartAsync();
    public async Task DisposeAsync() => await _container.DisposeAsync();
}

public class OrderRepositoryTests : IClassFixture<DatabaseFixture>
{
    private readonly AppDbContext _context;

    public OrderRepositoryTests(DatabaseFixture fixture)
    {
        var options = new DbContextOptionsBuilder<AppDbContext>()
            .UseNpgsql(fixture.ConnectionString)
            .Options;
        _context = new AppDbContext(options);
        _context.Database.EnsureCreated();
    }

    [Fact]
    public async Task Add_WithValidOrder_PersistsToDatabase()
    {
        var repo = new OrderRepository(_context);
        var order = new Order { CustomerId = 1, TotalPrice = 500 };

        await repo.AddAsync(order);
        await _context.SaveChangesAsync();

        var saved = await _context.Orders.FindAsync(order.Id);
        Assert.Equal(500, saved.TotalPrice);
    }
}