Mert Özen Her Satırda Daha İleri
Backend .NET Core Web API — #25

Integration test: WebApplicationFactory ile API'yi uçtan uca test etmek

Mert Özen 21 Ağu 2026 13 dk 2 görüntülenme
Integration test: WebApplicationFactory ile API'yi uçtan uca test etmek

Integration test'i işliyoruz: unit test'ten farkı, parçalar bir araya gelince test etmek ne demek, WebApplicationFactory ile gerçek bir istek uçtan uca akıtmak ve test için ayrı bir veritabanı kullanmak.

Önceki yazıda unit test'i öğrendik: kodun parçalarını tek tek, izole biçimde sınadık. Bağımlılıkları Moq ile taklit ettik, gerçek veritabanına hiç dokunmadık. Bu güçlü bir yaklaşım ama bir kör noktası var. Parçaların her biri tek başına doğru çalışabilir ama bir araya geldiklerinde bozulabilirler. İşte integration test tam bu boşluğu kapatır. Bugün API'yi bir bütün olarak, gerçek bir istek uçtan uca akarken nasıl test edeceğimizi konuşuyoruz.

Unit Test ile Integration Test Farkı

İki test türünü net biçimde ayıralım. Unit test, tek bir parçayı diğerlerinden yalıtıp sınar; "bu metot, kendi başına doğru mu çalışıyor?" sorusunu sorar. Integration test ise parçaların birleşimini sınar; "bu parçalar bir araya geldiğinde doğru mu çalışıyor?" sorusunu sorar. Biri mikroskopla tek bir hücreye bakar, diğeri organların birlikte çalışıp çalışmadığını kontrol eder.

Bir orkestra benzetmesi işi netleştirir. Unit test, her müzisyenin kendi enstrümanını doğru çaldığını tek tek kontrol etmektir. Ama her müzisyen tek başına harika çalsa bile, hepsi birlikte çaldığında uyum tutmayabilir. Integration test, işte o topluca çalınan performansı dinler: ritim tutuyor mu, sesler uyumlu mu? Gerçek bir API'de de parçalar (controller, servis, veritabanı) tek tek doğru olsa bile, birleşim yerlerinde sorun çıkabilir. Integration test bunu yakalar.

Integration Test Neyi Sınar?

Bir integration test'te, gerçek bir HTTP isteği API'nin en dışından girer ve tüm katmanlardan geçer: routing, model binding, validation, controller, servis, veritabanı... Sonra dönen cevabı kontrol edersin. Yani sadece bir metodu değil, bir isteğin baştan sona izlediği tüm yolu sınamış olursun. Bu, gerçek kullanımı en yakın taklit eden test türüdür.

Farkı hissettin mi? Unit test'te bağımlılıkları taklit ediyorduk; integration test'te ise mümkün olduğunca gerçeğini kullanırız. Amaç, üretimde ne olacaksa ona en yakın koşulu kurmak. Elbette bunun bir bedeli var: integration test'ler unit test'lerden daha yavaştır çünkü daha çok şey gerçekten çalışır. Bu yüzden ikisi birbirini tamamlar; çok sayıda hızlı unit test, daha az sayıda kapsamlı integration test.

WebApplicationFactory: Test İçin Uygulamayı Ayağa Kaldırmak

Peki gerçek bir istek akıtmak için API'yi bir yerde çalıştırmamız gerekmiyor mu? İşte .NET'in bu iş için sunduğu araç WebApplicationFactory. Bu sınıf, API'nin tamamını test ortamında, bellekte ayağa kaldırır. Gerçek bir sunucuya, gerçek bir port'a ihtiyaç yok; uygulama test sürecinin içinde, gerçeğine çok yakın biçimde çalışır.

Bunu bir uçuş simülatörü gibi düşün. Pilotu test etmek için gerçek bir uçağı havalandırmazsın; onun yerine, gerçeğine çok yakın davranan bir simülatör kurarsın. Pilot orada gerçek koşulları yaşar ama güvenli ve kontrollü bir ortamda. WebApplicationFactory de API'n için bu simülatörü kurar: uygulama gerçekten çalışır, gerçek istekleri işler, ama hepsi test ortamının içinde, güvenle.

İlk Integration Test'i Yazmak

Bu araç, önceki yazıda kurduğumuz test projesinde çalışır. Önce gerekli test paketini ekleriz:

dotnet add KullaniciApi.Tests package Microsoft.AspNetCore.Mvc.Testing

Sonra bir test sınıfı yazarız. Bu sınıf, WebApplicationFactory'yi kullanarak uygulamayı ayağa kaldırır ve ona gerçek HTTP istekleri gönderir:

public class UsersEndpointTests
    : IClassFixture>
{
    private readonly HttpClient _client;

    public UsersEndpointTests(WebApplicationFactory factory)
    {
        _client = factory.CreateClient();
    }

    [Fact]
    public async Task GetAll_ReturnsSuccessStatusCode()
    {
        // Act
        var response = await _client.GetAsync("/users");

        // Assert
        response.EnsureSuccessStatusCode();
    }
}

Adım adım bakalım. IClassFixture<WebApplicationFactory<Program>>, xUnit'e "bu test sınıfı için uygulamayı bir kez ayağa kaldır ve testler arasında paylaş" der. factory.CreateClient() ise bize bir HttpClient verir; bu, uygulamaya gerçek istekler gönderebileceğimiz bir kapı. Test içinde /users adresine gerçek bir GET isteği atıyoruz ve EnsureSuccessStatusCode ile cevabın başarılı bir durum kodu (2xx) dönüp dönmediğini kontrol ediyoruz. Dikkat et: burada hiçbir şeyi taklit etmedik; istek gerçekten tüm katmanlardan geçti.

Not: Program Sınıfının Görünür Olması

Yukarıdaki kodda WebApplicationFactory<Program> yazdık; yani test'in senin Program sınıfına erişmesi gerekiyor. Minimal hosting modelinde Program sınıfı varsayılan olarak test projesine kapalı olabilir. Bunu açmak için ana projenin Program.cs dosyasının en altına küçük bir satır eklersin:

public partial class Program { }

Bu tek satır, Program sınıfını test projesinin görebileceği hale getirir. İkinci yazıda konuştuğumuz minimal hosting modelinin küçük bir yan etkisi; bilince çözümü basit.

Test İçin Ayrı Veritabanı Kullanmak

Kritik bir konu var: integration test gerçek katmanlardan geçtiğine göre, veritabanına da dokunacak. Ama testlerinin senin gerçek verini bozmasını asla istemezsin. Bir test kullanıcı ekleyip silerse, bu senin canlı verinde değil, teste özel bir yerde olmalı. Bu yüzden testlerde ayrı, izole bir veritabanı kullanırız.

En yaygın yaklaşım, test sırasında uygulamanın veritabanı ayarını değiştirmektir. WebApplicationFactory bunu özelleştirmene izin verir: uygulama ayağa kalkarken, gerçek veritabanı yerine test için özel bir veritabanı (mesela her testte sıfırdan kurulan bir bellek-içi veritabanı ya da ayrı bir test SQLite dosyası) bağlarsın. Böylece her test temiz bir sayfayla başlar ve testler birbirini etkilemez. On beşinci yazıda öğrendiğimiz configuration ve ortam mantığı burada tam işe yarar: test ortamı için farklı ayar vermek, o bilgiyle çok doğal gelir.

İki Test Türünü Birlikte Kullanmak

Şunu netleştirelim: integration test, unit test'in yerini almaz. İkisi farklı sorulara cevap verir ve birlikte güçlüdürler. Pratikte dengeli bir yaklaşım şudur: kodun mantığının inceliklerini hızlı unit test'lerle sına; parçaların birleşimini ve gerçek akışı ise daha az sayıda ama kapsamlı integration test'le doğrula. Çok sayıda küçük unit test, saniyeler içinde çalışır ve mantık hatalarını yakalar. Daha az sayıda integration test ise "her şey bir araya gelince gerçekten çalışıyor mu?" güvencesini verir. İkisinin dengesi, sağlam bir test kültürünün temelidir.

Küçük Bir Deneme

Test projene WebApplicationFactory ile bir integration test ekle ve mevcut bir GET endpoint'ine gerçek bir istek at; dönen durum kodunu kontrol et. Sonra bir POST endpoint'i için test yaz: bir kullanıcı oluşturan istek gönder ve hem durum kodunu hem de dönen veriyi doğrula. İstersen bir adım ileri git ve test için ayrı bir veritabanı bağla, sonra bir kayıt ekleyip onu geri okuyan bir test yaz. Bu deney, integration test'in unit test'e kıyasla ne kadar farklı bir güven verdiğini net biçimde gösterir: artık sadece parçaları değil, bütünü sınıyorsun.

Bir sonraki yazıda caching konusuna geçiyoruz. API'nin sık istenen ama nadiren değişen verileri her seferinde veritabanından çekmek yerine geçici olarak saklayıp performansı nasıl artırdığını konuşacağız: in-memory cache ve Redis ile distributed cache. Testlerle doğruluğu güvence altına aldık; sırada hızı artırmak var.