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

Unit test: xUnit ve Moq ile kodun parçalarını izole test etmek

Mert Özen 21 Ağu 2026 13 dk 2 görüntülenme
Unit test: xUnit ve Moq ile kodun parçalarını izole test etmek

Unit test'i işliyoruz: bir birimi izole test etmek ne demek, neden test yazarız, xUnit ile test yapısı, Moq ile bağımlılıkları taklit etmek ve iyi test yazmanın temel ilkeleri.

API'n artık güvenli ve iyi belgelenmiş. Ama bir soru hâlâ havada duruyor: gerçekten doğru çalıştığından nasıl emin olacaksın? Her değişiklikten sonra tüm endpoint'leri elle tek tek denemek hem yorucu hem de güvenilmez. İşte test tam bu boşluğu doldurur. Bugün test dünyasına ilk adımı atıyoruz: unit test. Kodunun en küçük parçalarını, tek başlarına, otomatik olarak nasıl doğrulayacağımızı konuşuyoruz.

Neden Test Yazıyoruz?

Belki içinden "kodum çalışıyor zaten, niye bir de test yazayım?" diye geçiriyorsun. Haklı bir soru. Ama şunu düşün: bugün yazdığın kod çalışıyor olabilir, peki üç ay sonra başka bir yeri değiştirdiğinde bu kodun hâlâ çalıştığından emin olabilecek misin? Test yazmadan, her değişiklikte tüm uygulamayı elle kontrol etmen gerekir. Test yazdığındaysa, tek bir komutla yüzlerce kontrolü saniyeler içinde çalıştırırsın.

Test'i bir güvenlik ağı gibi düşün. Bir cambaz ip üstünde yürürken altındaki ağ ona düşse bile zarar görmeyeceği güvenini verir. Test de sana bunu verir: kodda cesurca değişiklik yaparsın, çünkü bir şeyi kırdığında testler anında haber verir. Testsiz kodda ise her değişiklik bir kumar; bir yeri düzeltirken başka bir yeri bozup bozmadığını asla tam bilemezsin.

Unit Test Nedir?

Unit test, kodunun en küçük anlamlı parçasını, yani genellikle tek bir metodu, izole biçimde test etmektir. Buradaki anahtar kelime "izole". Test ettiğin metot, dış dünyadan (veritabanından, ağdan, başka servislerden) bağımsız çalışmalı ki testin sadece o metodun mantığını ölçsün, başka bir şeyin arızasını değil.

Bir arabanın fabrikada test edilmesini düşün. Fren sistemini test ederken, arabanın tamamını yola çıkarmazlar; fren mekanizmasını ayrı bir düzenekte, kontrollü koşullarda dener. Böylece "fren çalışıyor mu?" sorusuna net cevap alırlar; motor ya da lastik devreye girip sonucu bulandırmaz. Unit test de böyle: bir parçayı diğerlerinden yalıtıp sadece onu sınar.

Test Projesini Kurmak

Testler, ana projenin içinde değil, ayrı bir test projesinde yaşar. .NET'te bunu komut satırından oluşturursun. Bu seride xUnit kullanacağız; .NET dünyasının en yaygın test framework'lerinden biri:

dotnet new xunit -n KullaniciApi.Tests

Sonra test projesinin, test edeceği ana projeyi görebilmesi için aralarına bir referans eklersin:

dotnet add KullaniciApi.Tests reference KullaniciApi

Artık test projen hazır ve ana projedeki sınıflara erişebiliyor. Bir de bağımlılıkları taklit etmek için Moq paketini ekleyeceğiz, ama ona birazdan geleceğiz.

İlk Testi Yazmak: AAA Deseni

İyi bir test genelde üç aşamadan oluşur ve bu yapıya AAA deseni denir: Arrange, Act, Assert. Türkçesiyle: hazırla, çalıştır, doğrula. Önce test için gereken ortamı kurarsın, sonra test ettiğin metodu çalıştırırsın, en sonda da sonucun beklediğin gibi olup olmadığını kontrol edersin. Basit bir örnekle görelim; iki sayıyı toplayan bir metodu test edelim:

public class CalculatorTests
{
    [Fact]
    public void Add_TwoNumbers_ReturnsSum()
    {
        // Arrange
        var calculator = new Calculator();

        // Act
        var result = calculator.Add(2, 3);

        // Assert
        Assert.Equal(5, result);
    }
}

Birkaç şeye dikkat et. Metodun başındaki [Fact], xUnit'e "bu bir testtir, çalıştır" der. Test metodunun adı da anlamlı: ne test edildiğini, hangi koşulda ve ne beklendiğini anlatıyor. Assert.Equal(5, result) ise işin kalbi: sonucun 5 olmasını bekliyoruz; öyleyse test geçer, değilse başarısız olur ve xUnit sana tam olarak neyin uyuşmadığını söyler. Testi çalıştırmak için dotnet test komutu yeterli.

Sorun: Ya Metot Bir Bağımlılığa İhtiyaç Duyuyorsa?

Toplama metodunu test etmek kolaydı çünkü hiçbir şeye ihtiyacı yoktu. Ama gerçek servislerimiz öyle değil. Onuncu yazıda yazdığımız UserService'i hatırla: içinde bir DbContext vardı, yani veritabanına bağımlıydı. Bu servisi test etmek istediğimizde bir çıkmaza gireriz: test sırasında gerçek veritabanına bağlanmak istemeyiz. Bu hem yavaş, hem kırılgan, hem de izole değil.

Peki servisi, gerçek veritabanı olmadan nasıl test edeceğiz? İşte burada taklit (mock) devreye giriyor. Fikir şu: bağımlılığın gerçeğini değil, onun gibi davranan sahte bir versiyonunu kullanırız. Servisimiz bir veritabanına konuştuğunu sanır ama aslında bizim kontrol ettiğimiz, önceden ne döneceğini söylediğimiz sahte bir nesneyle konuşur.

Moq ile Bağımlılıkları Taklit Etmek

Moq, tam da bu taklit nesneleri kolayca üretmemizi sağlayan bir kütüphane. Test projesine eklersin:

dotnet add KullaniciApi.Tests package Moq

Diyelim ki servisimiz bir IUserRepository arayüzüne bağımlı (onuncu yazıda arayüzlerin test kolaylığı sağladığından bahsetmiştik; işte tam da bu yüzden). Moq ile bu arayüzün sahte bir versiyonunu üretip ona ne döneceğini söyleriz:

[Fact]
public async Task GetById_ExistingUser_ReturnsUser()
{
    // Arrange
    var fakeRepo = new Mock();
    fakeRepo
        .Setup(r => r.GetByIdAsync(1))
        .ReturnsAsync(new User { Id = 1, Name = "Anna" });

    var service = new UserService(fakeRepo.Object);

    // Act
    var result = await service.GetByIdAsync(1);

    // Assert
    Assert.NotNull(result);
    Assert.Equal("Anna", result.Name);
}

Burada olan sihri açalım. Önce Mock<IUserRepository> ile sahte bir repository ürettik. Sonra Setup ile ona bir talimat verdik: "Sana GetByIdAsync(1) çağrısı gelirse, şu kullanıcıyı dön." Yani repository'nin davranışını biz belirledik. Ardından bu sahte nesneyi (fakeRepo.Object) servise verdik. Servis artık gerçek veritabanı yerine bizim kontrolümüzdeki sahte nesneyle çalışıyor. Testin geri kalanı, servisin bu veriyle doğru davranıp davranmadığını ölçüyor. Gerçek veritabanına hiç dokunmadık; test hem hızlı, hem izole, hem de güvenilir.

İyi Test Yazmanın İlkeleri

Test yazmak kolay ama iyi test yazmak biraz özen ister. Birkaç temel ilke: Her test tek bir şeyi sınamalı; bir testte on farklı şey kontrol edersen, başarısız olduğunda hangisinin bozulduğunu anlamak zorlaşır. Testler birbirinden bağımsız olmalı; birinin çalışması diğerinin sonucuna dayanmamalı. Test adları açıklayıcı olmalı, öyle ki test başarısız olduğunda adına bakıp neyin bozulduğunu anlayabilesin. Ve testler hızlı olmalı; yavaş testler zamanla çalıştırılmaz olur, çalıştırılmayan test de değersizdir.

Küçük Bir Deneme

Önce basit bir test projesi kur ve içine, hiçbir bağımlılığı olmayan küçük bir metot için (mesela bir string'i tersine çeviren bir fonksiyon) AAA desenine uygun bir test yaz. dotnet test ile çalıştır ve yeşil sonucu gör. Sonra bir adım ileri git: elindeki servislerden birini al, bağımlılığını Moq ile taklit et ve servisin mantığını izole biçimde test et. Bilerek bir de başarısız test yaz (mesela yanlış bir değer bekle) ve xUnit'in hatayı nasıl raporladığını gözlemle. Bu deney, testin soyut bir kavram olmaktan çıkıp elindeki somut bir güvenceye dönüşmesini sağlar.

Bir sonraki yazıda integration test'e geçeceğiz. Unit test parçaları tek tek sınıyordu; integration test ise parçaların bir araya gelince doğru çalışıp çalışmadığını, gerçek bir istek uçtan uca akarken kontrol eder. WebApplicationFactory ile API'ni bütünüyle nasıl test edeceğimizi konuşacağız. Bugün öğrendiğin izolasyon mantığı, orada tam tersi bir bakış açısıyla tamamlanacak.