← Back to list

.NET’te Test Yazmanın Tam Rehberi — Bölüm 1: xUnit + Moq ile Sıfırdan Profesyonel Testler

Önce Şunu Soralım: Neden Test Yazıyoruz?

Onur Nafi Güzel · 2026-04-24 14:06 · 0 claps · 17.1 min read
#unit-testing #unittest #xunit
Open on Medium ↗
Wiki topics: MM · Multimodal & Generative Media

.NET’te Test Yazmanın Tam Rehberi — Bölüm 1: xUnit + Moq ile Sıfırdan Profesyonel Testler

Önce Şunu Soralım: Neden Test Yazıyoruz?

Düşünün, bir e-ticaret sitesinde çalışıyorsunuz. Ödeme sistemine küçük bir değişiklik yaptınız. Her şey normal görünüyor, kodu canlıya aldınız. Gece 2'de telefon çalıyor: “Ödemeler geçmiyor.”

İşte birim testleri tam olarak bu senaryoyu engeller. Kodu canlıya almadan önce, yazdığınız her parçanın beklediğiniz gibi çalıştığını otomatik olarak doğrular.

Test yazmanın size kazandırdıkları:

  • Yeni özellik eklerken eski kodu bozmadığınızı anlık görürsünüz
  • “Bu metot ne yapıyor?” sorusunun cevabı test dosyasında yazılıdır
  • Refactoring yaparken kendinize güvenirsiniz
  • Sabah 2'de telefon açılmaz

Bu Makalede Ne Öğreneceksiniz?

Bu makale hiç test yazmamış biri için tasarlandı. En basit testten başlayıp adım adım ilerleyeceğiz:

  1. İlk test — sadece bir metot çağırıp sonucu kontrol etmek
  2. Parametreli testler — aynı testi farklı verilerle tekrar etmek
  3. Exception testleri — “hata bekliyorum” demek
  4. Mock nedir, neden lazım? — bağımlılıkları simüle etmek
  5. Moq kütüphanesi — tüm detaylarıyla
  6. Gerçek dünya senaryosu — her şeyi bir araya getirmek

Kurulum

# Yeni bir test projesi oluştur
dotnet new xunit -n MyProject.Tests

# Moq ve okunabilir assertion kütüphanesi ekle
dotnet add package Moq
dotnet add package FluentAssertions

Projenizin .csproj dosyası şöyle görünmeli:

<ItemGroup>
  <PackageReference Include="Microsoft.NET.Test.Sdk"       Version="17.x.x" />
  <PackageReference Include="xunit"                        Version="2.x.x" />
  <PackageReference Include="xunit.runner.visualstudio"    Version="2.x.x" />
  <PackageReference Include="Moq"                          Version="4.x.x" />
  <PackageReference Include="FluentAssertions"             Version="6.x.x" />
</ItemGroup>

Testleri çalıştırmak için terminale şunu yazmanız yeterli:

dotnet test

BÖLÜM 1 — İlk Adım: En Basit Test

Test Nasıl Çalışır? AAA Deseni

Her iyi test üç bölümden oluşur. Bu desene AAA denir:

  • Arrange (Hazırla): Test için gerekli nesneleri oluştur, değişkenleri tanımla
  • Act (Çalıştır): Test etmek istediğin metodu çağır
  • Assert (Doğrula): Sonucun beklediğin gibi olduğunu kontrol et

Bu üç bölümü kodda her zaman yorum satırıyla ayırın. Hem sizi hem de sonra kodu okuyacak kişiyi çok kurtarır.

[Fact] — Tekil Test

[Fact] attribute'u xUnit'e "bu bir test metodudur" der. En temel test türüdür. Parametre almaz, her seferinde aynı şekilde çalışır.

Önce test edeceğimiz sınıfı yazalım:

// Bu, test ETTİĞİMİZ asıl kod (production kodu)
public class Hesaplama
{
    public int Topla(int a, int b)
    {
        return a + b;
    }

    public int Bol(int a, int b)
    {
        if (b == 0)
            throw new DivideByZeroException("Sıfıra bölme yapılamaz.");
        return a / b;
    }
}

Şimdi bu sınıf için test yazalım:

// Bu, test YAZAN dosya (test projesi)
public class HesaplamaTests
{
    [Fact]
    public void Topla_IkiPozitifSayi_DogruSonucDonmeli()
    {
        // Hazırla: test için gerekli nesneyi oluştur
        var hesaplama = new Hesaplama();

        // Çalıştır: test etmek istediğimiz metodu çağır
        int sonuc = hesaplama.Topla(3, 4);

        // Doğrula: sonucun beklediğimiz gibi olduğunu kontrol et
        Assert.Equal(7, sonuc);
    }
}

Test adı üç parçadan oluşur: [TestEdilenMetot]_[Senaryo]_[BeklenenSonuc]

Bu isimlendirme kuralı çok önemlidir. Test başarısız olduğunda, ismine bakarak ne test ettiğinizi ve neyin bozulduğunu anlarsınız.

Assert Metotları — Sonucu Nasıl Doğrularız?

Assert, xUnit'in "kontrol et ve sonuç çıkar" aracıdır. Temel metotları şunlardır:

// Değer eşit mi?
Assert.Equal(7, sonuc);
Assert.NotEqual(0, sonuc);

// Nesne null mu, değil mi?
Assert.Null(nesne);
Assert.NotNull(nesne);

// Koşul doğru mu, yanlış mı?
Assert.True(sonuc > 0);
Assert.False(sonuc < 0);

// Liste boş mu, değil mi?
Assert.Empty(liste);
Assert.NotEmpty(liste);

// Liste belirli bir eleman içeriyor mu?
Assert.Contains(5, liste);
Assert.DoesNotContain(99, liste);

// Nesne belirli bir tipte mi?
Assert.IsType<string>(nesne);

// Değer belirli bir aralıkta mı? (1 ile 100 arasında)
Assert.InRange(deger, 1, 100);

// Metin belirli bir ifadeyle başlıyor/bitiyor mu?
Assert.StartsWith("Merhaba", metin);
Assert.EndsWith("Dünya",    metin);
Assert.Contains("xunit",    metin);

BÖLÜM 2 — Parametreli Testler

[Theory] + [InlineData] — Aynı Testi Farklı Verilerle Çalıştırmak

[Theory]
[InlineData(3,   4,   7)]   // 3 + 4 = 7 olmalı
[InlineData(0,   0,   0)]   // 0 + 0 = 0 olmalı
[InlineData(-5,  5,   0)]   // -5 + 5 = 0 olmalı
[InlineData(100, 200, 300)] // 100 + 200 = 300 olmalı
public void Topla_FarkliGirdiler_DogruSonucDonmeli(int a, int b, int expectedResult)
{
    // Hazırla
    var hesaplama = new Hesaplama();

    // Çalıştır
    int sonuc = hesaplama.Topla(a, b);

    // Doğrula
    Assert.Equal(expectedResult, sonuc);
}

Bu tek metot, dotnet test çalıştırıldığında 4 ayrı test olarak görünür. Biri başarısız olsa bile diğerleri çalışmaya devam eder.

[MemberData] — Veriyi Ayrı Bir Yerden Almak

[InlineData] yalnızca basit değerler (int, string, bool) kabul eder. Karmaşık nesneler veya çok sayıda veri varsa [MemberData] kullanırsınız.

public class UrunFiyatTests
{
    // Test verilerini ayrı bir property'de tanımlıyoruz
    // IEnumerable<object[]> döndürmesi zorunlu
    public static IEnumerable<object[]> KdvVerileri =>
        new List<object[]>
        {
            // { netFiyat, kdvOrani, beklenenToplamFiyat }
            new object[] { 100m,  0.18m, 118m  },
            new object[] { 200m,  0.08m, 216m  },
            new object[] { 0m,    0.18m, 0m    },
            new object[] { 1000m, 0.20m, 1200m }
        };

    [Theory]
    [MemberData(nameof(KdvVerileri))] // nameof ile string hatası önlenir
    public void KdvHesapla_NetFiyat_DogruToplamFiyatDonmeli(
        decimal netPrice,
        decimal vatRate,
        decimal expectedTotal)
    {
        // Hazırla
        var vergiHesaplayici = new VergiHesaplayici();

        // Çalıştır
        decimal toplamFiyat = vergiHesaplayici.KdvEkle(netPrice, vatRate);

        // Doğrula
        Assert.Equal(expectedTotal, toplamFiyat);
    }
}

[ClassData] — Veriyi Tamamen Ayrı Bir Sınıfa Taşımak

Test verisi büyüdükçe ya da birden fazla test sınıfında kullanılacaksa, veriyi kendi sınıfına taşımak en temiz çözümdür:

// Veriyi taşıyan sınıf — IEnumerable<object[]> implement etmeli
public class EmailDogrulamaVerileri : IEnumerable<object[]>
{
    public IEnumerator<object[]> GetEnumerator()
    {
        yield return new object[] { "ahmet@test.com",  true  }; // geçerli email
        yield return new object[] { "gecersiz-email",  false }; // @ işareti yok
        yield return new object[] { "",                false }; // boş string
        yield return new object[] { "a@b.c",           true  }; // minimum geçerli
    }

    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

public class EmailDogrulamaTests
{
    [Theory]
    [ClassData(typeof(EmailDogrulamaVerileri))]
    public void EmailDogrula_FarkliGirdiler_DogruSonucDonmeli(
        string email,
        bool expectedResult)
    {
        var dogrulay = new EmailDogrulayici();
        bool sonuc = dogrulay.Gecerli(email);
        Assert.Equal(expectedResult, sonuc);
    }
}

BÖLÜM 3 — Hata Testleri

Exception Testleri — “Bu Hata Fırlamalı” Demek

Bazen bir metodun hata fırlatmasını bekliyoruz. Örneğin sıfıra bölme işleminde DivideByZeroException fırlatılmalıdır. Bunu test etmek için Assert.Throws kullanırız.

[Fact]
public void Bol_SifiraBolunce_DivideByZeroExceptionFirlatmali()
{
    // Hazırla
    var hesaplama = new Hesaplama();

    // Çalıştır ve Doğrula
    // Lambda içindeki kod çalıştığında DivideByZeroException fırlatılmazsa test başarısız olur
    Assert.Throws<DivideByZeroException>(() => hesaplama.Bol(10, 0));
}

Hata mesajını da kontrol etmek istiyorsanız:

[Fact]
public void Bol_SifiraBolunce_DogruHataMesajiIleException()
{
    var hesaplama = new Hesaplama();

    // Throws metodunun döndürdüğü exception nesnesini yakalıyoruz
    var exception = Assert.Throws<DivideByZeroException>(
        () => hesaplama.Bol(10, 0)
    );

    // Hata mesajının doğru olduğunu da kontrol ediyoruz
    Assert.Equal("Sıfıra bölme yapılamaz.", exception.Message);
}

Async metotlarda ise ThrowsAsync kullanılır:

[Fact]
public async Task OdemeYap_GecersizKart_PaymentExceptionFirlatmali()
{
    var servis = new OdemeServisi();

    await Assert.ThrowsAsync<PaymentException>(
        () => servis.OdemeYapAsync(kartNo: "0000-0000-0000-0000", tutar: 100)
    );
}

BÖLÜM 4 — Mock Nedir? Neden Lazım?

Bu bölüm makalenin en kritik kısmı. Mock kavramını anlamadan Moq kütüphanesini kullanmak mümkün değil.

Sorun: Bağımlılıklar

Gerçek hayat kodlarında bir sınıf nadiren yalnız çalışır. Bir SiparisServis düşünün:

public class SiparisServis
{
    public Siparis Olustur(int urunId, int adet)
    {
        // 1. Veritabanından ürünü çek
        var db = new VeritabaniContext();
        var urun = db.Urunler.Find(urunId);

        // 2. Stok kontrol servisini çağır
        var stokServis = new StokServis();
        bool stokVar = stokServis.Kontrol(urunId, adet);

        // 3. E-posta gönder
        var emailServis = new EmailServis();
        emailServis.Gonder("siparis@ornek.com", "Siparişiniz alındı");

        return new Siparis { /* ... */ };
    }
}

Bu kodu test etmek istediğinizde şu sorunlarla karşılaşırsınız:

  • Veritabanı: Test sırasında gerçek bir veritabanı bağlantısı gerekiyor. Yavaş, kırılgan, temizlenmesi gerekiyor.
  • E-posta servisi: Her testte gerçekten e-posta mı gönderelim? Hem yavaş hem saçma.
  • Harici API: İnternet bağlantısı yoksa test çalışmaz.
  • Tahmin edilemezlik: Veritabanında hangi verinin olduğunu bilemezsiniz.

Test etmek istediğiniz şey SiparisServis'in kendi mantığı, yoksa veritabanının veya e-posta servisinin çalışması değil.

Çözüm: Dependency Injection + Interface

Önce bağımlılıkları dışarıdan enjekte edilebilir hale getiririz. Bunun için interface kullanırız:

// Her bağımlılık için bir interface tanımlıyoruz
public interface IUrunRepository
{
    Urun? GetirById(int id);
}

public interface IStokServis
{
    bool StokVarMi(int urunId, int adet);
}

public interface IEmailServis
{
    void Gonder(string alici, string mesaj);
}

// Artık SiparisServis bağımlılıklarını dışarıdan alıyor
public class SiparisServis
{
    private readonly IUrunRepository _urunRepository;
    private readonly IStokServis     _stokServis;
    private readonly IEmailServis    _emailServis;

    // Constructor injection: bağımlılıklar dışarıdan veriliyor
    public SiparisServis(
        IUrunRepository urunRepository,
        IStokServis     stokServis,
        IEmailServis    emailServis)
    {
        _urunRepository = urunRepository;
        _stokServis     = stokServis;
        _emailServis    = emailServis;
    }

    public Siparis Olustur(int urunId, int adet)
    {
        var urun = _urunRepository.GetirById(urunId)
            ?? throw new ArgumentException($"Ürün bulunamadı: {urunId}");

        if (!_stokServis.StokVarMi(urunId, adet))
            throw new StokYetersizException("Yeterli stok bulunmuyor.");

        _emailServis.Gonder("siparis@ornek.com", $"Siparişiniz alındı: {urun.Ad}");

        return new Siparis
        {
            UrunId      = urunId,
            Adet        = adet,
            ToplamFiyat = urun.Fiyat * adet
        };
    }
}

Artık SiparisServis, somut sınıflara değil interface'lere bağlı. Bu sayede test sırasında gerçek IUrunRepository yerine sahte (mock) bir IUrunRepository verebiliriz.

Mock Nedir? Bir Benzetme

Mock nesneyi şöyle düşünün: Bir aktör film setinde gerçek bir araba kullanmak yerine sahte bir araba kullanır. Sahte araba tam da gerçeği gibi görünür, kapılar açılır, direksiyon döner — ama motoru yoktur.

Mock da böyledir: Gerçek IUrunRepository yerine "id=1 gelirse şu ürünü döndür" diyebileceğiniz sahte bir nesne oluşturursunuz. Veritabanına bağlanmaz, ağ çağrısı yapmaz — ama arayüzün tüm metodlarına sahiptir.

BÖLÜM 5 — Moq Kütüphanesi: Tüm Detaylarıyla

Mock Oluşturma ve Kullanma

// Mock nesnesini oluştur
var mockRepo = new Mock<IUrunRepository>();

// Mock nesnesini sisteme ver
// .Object ile gerçek interface nesnesini alırsınız
var servis = new SiparisServis(mockRepo.Object, ...);

Mock<T> sınıfının iki önemli parçası:

  • mock (Mock nesnesi): Davranışları tanımlamak ve doğrulama yapmak için
  • mock.Object (T nesnesi): Servisinize gerçek nesne olarak vermek için

Setup — "Şu Metot Çağrılırsa, Şunu Yap" Demek

Setup, mock nesnesine bir davranış tanımlar. "Bu metot şu parametreyle çağrılırsa, şu değeri döndür" demektir.

// GetirById(1) çağrılırsa → bu ürünü döndür
mockRepo.Setup(repo => repo.GetirById(1))
        .Returns(new Urun { Id = 1, Ad = "Laptop", Fiyat = 25000 });

Buradaki repo => ifadesi bir lambda expression'dır. "repo değişkenini kullanarak şu metodu çağırdığında..." demektir. repo'nun adı önemli değil, r veya x de yazabilirdiniz.

It Sınıfı — "Herhangi Bir Değer" Demek

It Moq'un en önemli kavramlarından biridir. Türkçeye "O" veya "Herhangi biri" olarak çevrilebilir. Argüman eşleştirici olarak görev yapar.

Sorun şu: Setup(r => r.GetirById(1)) yazarsanız, bu setup yalnızca GetirById(1) çağrısında devreye girer. GetirById(2) veya GetirById(999) için geçersizdir.

Peki ya “hangi id gelirse gelsin, aynı şeyi yap” demek istiyorsak? İşte It burada devreye girer:

// Sadece 1 parametresi için çalışır
mockRepo.Setup(r => r.GetirById(1))
        .Returns(new Urun { Ad = "Laptop" });

// Herhangi bir int değer için çalışır - It.IsAny<int>()
mockRepo.Setup(r => r.GetirById(It.IsAny<int>()))
        .Returns(new Urun { Ad = "Test Ürünü" });

It sınıfının tüm metotları:

// ─── It.IsAny<T>() ──────────────────────────────────────────────
// "Bu parametreye herhangi bir T değer gelebilir" demektir.
// Parametrenin ne olduğu önemli değilse kullanılır.
mockRepo.Setup(r => r.GetirById(It.IsAny<int>()))
        .Returns(new Urun());

mockServis.Setup(s => s.Gonder(It.IsAny<string>(), It.IsAny<string>()))
          .Returns(true);
// ─── It.Is<T>(koşul) ────────────────────────────────────────────
// "Bu parametre şu koşulu sağlamalı" demektir.
// Belirli bir kurala uyan değerler için kullanılır.
mockRepo.Setup(r => r.GetirById(It.Is<int>(id => id > 0)))
        .Returns(new Urun { Ad = "Geçerli Ürün" });
mockRepo.Setup(r => r.GetirById(It.Is<int>(id => id > 0 && id < 1000)))
        .Returns(new Urun { Ad = "Küçük ID'li Ürün" });
// Birden fazla koşul - string için örnek
mockServis.Setup(s => s.Ara(It.Is<string>(q => q.Length > 3 && !q.Contains(" "))))
          .Returns(new List<Urun>());
// ─── It.IsIn<T>(değerler) ────────────────────────────────────────
// "Bu parametre şu listedeki değerlerden biri olmalı" demektir.
mockRepo.Setup(r => r.GetirById(It.IsIn(1, 2, 3)))
        .Returns(new Urun { Ad = "Listede Olan Ürün" });
// ─── It.IsNotIn<T>(değerler) ─────────────────────────────────────
// "Bu parametre şu listedeki değerlerden biri OLMAMALI" demektir.
mockRepo.Setup(r => r.GetirById(It.IsNotIn(0, -1, -2)))
        .Returns(new Urun { Ad = "Geçerli Ürün" });
// ─── It.IsInRange<T>(min, max, aralık türü) ──────────────────────
// "Bu parametre şu aralıkta olmalı" demektir.
// Range.Inclusive: min ve max dahil | Range.Exclusive: min ve max hariç
mockRepo.Setup(r => r.GetirById(It.IsInRange(1, 100, Range.Inclusive)))
        .Returns(new Urun { Ad = "Aralıkta Ürün" });
// ─── It.IsRegex(desen) ───────────────────────────────────────────
// "Bu string parametre şu regex deseniyle eşleşmeli" demektir.
// Sadece string parametreler için kullanılır.
mockEmailServis.Setup(e => e.Gonder(It.IsRegex(@"^[\w-]+@[\w-]+\.\w+$"), It.IsAny<string>()))
               .Returns(true);
// ─── It.IsNull<T>() ve It.IsNotNull<T>() ─────────────────────────
// Parametrenin null veya null olmayan olmasını bekler.
mockRepo.Setup(r => r.Ekle(It.IsNotNull<Urun>()))
        .Verifiable();

Returns ve ReturnsAsync — Dönüş Değeri Tanımlamak

// Sabit bir değer döndür
mockRepo.Setup(r => r.GetirById(1))
        .Returns(new Urun { Id = 1, Ad = "Laptop" });

// null döndür (ürün bulunamadı senaryosu)
mockRepo.Setup(r => r.GetirById(999))
        .Returns((Urun?)null);

// Async metotlar için ReturnsAsync kullanılır
mockRepo.Setup(r => r.GetirByIdAsync(1))
        .ReturnsAsync(new Urun { Id = 1, Ad = "Laptop" });

// Her çağrıda farklı değer döndür (sırayla)
mockRepo.SetupSequence(r => r.GetirById(It.IsAny<int>()))
        .Returns(new Urun { Ad = "Birinci Çağrıda Bu Döner" })
        .Returns(new Urun { Ad = "İkinci Çağrıda Bu Döner" })
        .Returns(null);         // Üçüncü çağrıda null döner

Throws — Hata Fırlatmasını Simüle Etmek

Veritabanı bağlantı hatası, ağ zaman aşımı gibi senaryoları simüle etmek için kullanılır:

// Metot çağrıldığında exception fırlat
mockRepo.Setup(r => r.GetirById(It.IsAny<int>()))
        .Throws(new Exception("Veritabanı bağlantısı kesildi."));

// Async metot için
mockRepo.Setup(r => r.GetirByIdAsync(It.IsAny<int>()))
        .ThrowsAsync(new TimeoutException("İstek zaman aşımına uğradı."));

// Belirli bir parametreye özel hata
mockRepo.Setup(r => r.GetirById(-1))
        .Throws(new ArgumentException("ID negatif olamaz."));

Callback — Metot Çağrıldığında Yan Etki Üretmek

Bir metot çağrıldığında sadece değer döndürmekle kalmayıp, aynı zamanda bir şey kaydetmek veya bir yan etki üretmek isteyebilirsiniz:

// Ekle() çağrıldığında kaydı takip et
var eklenenUrunler = new List<Urun>();

mockRepo.Setup(r => r.Ekle(It.IsAny<Urun>()))
        .Callback<Urun>(urun =>
        {
            // Her Ekle() çağrısında bu lambda çalışır
            eklenenUrunler.Add(urun);
        });

// Şimdi servisi çalıştır
servis.TopluEkle(new[] { urun1, urun2 });

// Callback sayesinde hangi ürünlerin eklendiğini görebiliyoruz
Assert.Equal(2, eklenenUrunler.Count);
Assert.Contains(eklenenUrunler, u => u.Ad == "Laptop");

Verify — Metodun Gerçekten Çağrıldığını Doğrulamak

Assert metodun sonucunu doğrularken, Verify metodun çağrılıp çağrılmadığını doğrular. "Bu metot beklediğim şekilde çağrıldı mı?" sorusunun cevabıdır.

// ─── Times.Once ─────────────────────────────────────────────────
// Metot tam olarak 1 kez çağrılmış mı?
mockEmailServis.Verify(
    e => e.Gonder(It.IsAny<string>(), It.IsAny<string>()),
    Times.Once
);

// ─── Times.Never ────────────────────────────────────────────────
// Metot hiç çağrılmamış mı?
// Hata durumunda email gönderilmemeli senaryosunda kullanılır.
mockEmailServis.Verify(
    e => e.Gonder(It.IsAny<string>(), It.IsAny<string>()),
    Times.Never
);

// ─── Times.Exactly(n) ────────────────────────────────────────────
// Metot tam olarak n kez çağrılmış mı?
mockRepo.Verify(r => r.Ekle(It.IsAny<Urun>()), Times.Exactly(3));

// ─── Times.AtLeastOnce ──────────────────────────────────────────
// Metot en az 1 kez çağrılmış mı?
mockRepo.Verify(r => r.Kaydet(), Times.AtLeastOnce);

// ─── Times.AtLeast(n) ────────────────────────────────────────────
// Metot en az n kez çağrılmış mı?
mockRepo.Verify(r => r.Kaydet(), Times.AtLeast(2));

// ─── Times.AtMost(n) ─────────────────────────────────────────────
// Metot en fazla n kez çağrılmış mı?
mockRepo.Verify(r => r.Kaydet(), Times.AtMost(5));

// ─── Times.Between(min, max, aralık türü) ────────────────────────
// Metot min ile max arasında kaç kez çağrılmış?
mockRepo.Verify(r => r.Kaydet(), Times.Between(1, 3, Range.Inclusive));

Belirli parametrelerle çağrılıp çağrılmadığını da doğrulayabilirsiniz:

// Ekle() metodu, Adı "Laptop" olan bir ürünle çağrılmış mı?
mockRepo.Verify(
    r => r.Ekle(It.Is<Urun>(u => u.Ad == "Laptop")),
    Times.Once
);

// Email servisi, içinde "sipariş" geçen bir mesajla çağrılmış mı?
mockEmailServis.Verify(
    e => e.Gonder(It.IsAny<string>(), It.Is<string>(m => m.Contains("sipariş"))),
    Times.Once
);

MockBehavior — Mock'un Genel Davranışı

Moq’da iki farklı mock davranışı vardır:

// ─── Loose (varsayılan) ──────────────────────────────────────────
// Setup edilmemiş metotlar varsayılan değer döndürür (null, 0, false).
// Esnek ama dikkatsiz kullanımda hataları gizleyebilir.
var looseMock = new Mock<IUrunRepository>();
// GetirById() için setup yapılmadıysa null döner, exception fırlatmaz

// ─── Strict ──────────────────────────────────────────────────────
// Setup edilmemiş HER metot çağrısı exception fırlatır.
// Daha dikkatli ama daha güvenli.
var strictMock = new Mock<IUrunRepository>(MockBehavior.Strict);
// strictMock üzerinde her kullanılacak metot mutlaka Setup edilmeli
strictMock.Setup(r => r.GetirById(1)).Returns(new Urun());

Başlangıç için Loose (varsayılan) kullanmanız önerilir. Testleriniz olgunlaştıkça Strict'e geçmeyi düşünebilirsiniz.

BÖLÜM 6 — Test Organizasyonu ve İleri Özellikler

Fixture — Ortak Kurulum Kodu

Bir test sınıfındaki tüm testler aynı nesneye ihtiyaç duyuyorsa, her testte ayrı ayrı oluşturmak yerine constructor’da bir kez oluşturabilirsiniz.

xUnit’te her test için yeni bir sınıf instance’ı oluşturulur. Bu sayede testler birbirini etkilemez.

public class UrunServisTests : IDisposable
{
    // Tüm testlerin kullandığı ortak nesneler
    private readonly Mock<IUrunRepository>  _mockRepo;
    private readonly Mock<IStokServis>      _mockStok;
    private readonly UrunServis             _servis;

    // Constructor: Her test BAŞLAMADAN ÖNCE çalışır
    public UrunServisTests()
    {
        // Mock nesnelerini bir kez oluştur
        _mockRepo = new Mock<IUrunRepository>();
        _mockStok = new Mock<IStokServis>();

        // Servisi inject edilen mock nesneleriyle kur
        _servis = new UrunServis(_mockRepo.Object, _mockStok.Object);
    }

    // Her test bu ortak nesneyi kullanır
    [Fact]
    public void GetirById_MevcutId_UrunDonmeli()
    {
        _mockRepo.Setup(r => r.GetirById(1))
                 .Returns(new Urun { Id = 1, Ad = "Laptop" });

        var urun = _servis.GetirById(1);

        Assert.NotNull(urun);
    }

    // Dispose: Her test BİTTİKTEN SONRA çalışır
    // Veritabanı bağlantısı, dosya handle'ı gibi kaynakları temizlemek için
    public void Dispose()
    {
        // Mock nesneleri için genellikle gerekmez
        // Gerçek kaynaklar (DbContext, HttpClient vs.) varsa burada kapat
    }
}

IClassFixture<T> — Pahalı Nesneleri Paylaşmak

Bazı nesnelerin oluşturulması pahalıdır: HTTP client, test veritabanı bağlantısı, Docker container gibi. Bunları her test için değil, tüm sınıf için bir kez oluşturmak istersiniz.

// Paylaşılacak nesneyi tutan sınıf
public class VeritabaniFixture : IDisposable
{
    public AppDbContext Db { get; }

    public VeritabaniFixture()
    {
        // Bu sınıftaki TÜM testler başlamadan önce bir kez çalışır
        Db = new AppDbContext(new DbContextOptionsBuilder<AppDbContext>()
            .UseInMemoryDatabase("TestDb")
            .Options);

        // Test verilerini ekle
        Db.Urunler.AddRange(
            new Urun { Id = 1, Ad = "Laptop",  Fiyat = 25000 },
            new Urun { Id = 2, Ad = "Klavye",  Fiyat = 500   }
        );
        Db.SaveChanges();
    }

    public void Dispose()
    {
        // Bu sınıftaki TÜM testler bittikten sonra bir kez çalışır
        Db.Dispose();
    }
}

// IClassFixture<T> ile fixture'ı sınıfa bağla
public class UrunRepositoryTests : IClassFixture<VeritabaniFixture>
{
    private readonly AppDbContext _db;

    // xUnit fixture'ı constructor'a enjekte eder
    public UrunRepositoryTests(VeritabaniFixture fixture)
    {
        _db = fixture.Db;
    }

    [Fact]
    public void GetirById_MevcutId_UrunDonmeli()
    {
        var urun = _db.Urunler.Find(1);
        Assert.NotNull(urun);
        Assert.Equal("Laptop", urun!.Ad);
    }
}

Testleri Gruplama ve Filtreleme — [Trait]

Projeniz büyüdükçe yüzlerce test birikir. [Trait] ile testlere etiket ekleyebilir ve sadece belirli etiketli testleri çalıştırabilirsiniz:

[Fact]
[Trait("Katman",  "Servis")]
[Trait("Özellik", "Sipariş")]
[Trait("Öncelik", "Kritik")]
public void Siparis_Olustur_BasariliOlmali() { }

[Fact]
[Trait("Katman",  "Repository")]
[Trait("Özellik", "Ürün")]
public void Urun_GetirById_DonmeMali() { }

Filtreleyerek çalıştırma:

# Sadece "Katman=Servis" testlerini çalıştır
dotnet test --filter "Katman=Servis"

# Sadece "Öncelik=Kritik" testlerini çalıştır
dotnet test --filter "Öncelik=Kritik"

# Belirli bir test metodunu çalıştır
dotnet test --filter "FullyQualifiedName~SiparisServisTests"

Test Atlamak — [Fact(Skip = "...")]

Henüz tamamlanmamış bir özellik varsa veya test geçici olarak devre dışı bırakılacaksa:

[Fact(Skip = "Ödeme entegrasyonu henüz tamamlanmadı — TICKET-4521")]
public void OdemeYap_KrediKarti_BasariliOlmali()
{
    // Bu test çalıştırılmaz, atlandı olarak raporlanır
}

Test Çıktısı — ITestOutputHelper

Console.WriteLine test sırasında görünmez. Bunun yerine xUnit'in kendi çıktı mekanizmasını kullanın:

public class DebugTests
{
    private readonly ITestOutputHelper _output;

    // xUnit ITestOutputHelper'ı otomatik enjekte eder
    public DebugTests(ITestOutputHelper output)
    {
        _output = output;
    }

    [Fact]
    public void Hesaplama_SonucuLogla()
    {
        var hesaplama = new Hesaplama();

        _output.WriteLine("Test başlıyor...");
        int sonuc = hesaplama.Topla(3, 4);
        _output.WriteLine($"Hesaplama sonucu: {sonuc}");

        Assert.Equal(7, sonuc);
        _output.WriteLine("Test başarıyla tamamlandı.");
    }
}

BÖLÜM 7 — FluentAssertions: Daha Okunabilir Testler

Standart Assert metodları işlevsel ama okunurluğu sınırlı. FluentAssertions kütüphanesi testleri neredeyse İngilizce bir cümle gibi okunur hale getirir. Üstelik test başarısız olduğunda çok daha açıklayıcı hata mesajları üretir.

using FluentAssertions;

// ─── Standart xUnit vs FluentAssertions ──────────────────────────

// xUnit
Assert.Equal("Laptop", urun.Ad);
Assert.NotNull(urun);
Assert.True(urun.Fiyat > 0);

// FluentAssertions — "urun, null olmamalı. Ad'ı 'Laptop' olmalı."
urun.Should().NotBeNull();
urun.Ad.Should().Be("Laptop");
urun.Fiyat.Should().BePositive();

// ─── Sayısal Karşılaştırmalar ─────────────────────────────────────
urun.Fiyat.Should().BeGreaterThan(0);
urun.Fiyat.Should().BeLessThanOrEqualTo(100_000);
urun.Fiyat.Should().BeInRange(1000, 50_000);

// ─── String Karşılaştırmalar ──────────────────────────────────────
urun.Ad.Should().StartWith("La");
urun.Ad.Should().EndWith("op");
urun.Ad.Should().Contain("apt");
urun.Ad.Should().HaveLength(6);
urun.Ad.Should().NotBeNullOrEmpty();

// ─── Koleksiyonlar ────────────────────────────────────────────────
urunler.Should().HaveCount(3);
urunler.Should().NotBeEmpty();
urunler.Should().Contain(u => u.Ad == "Laptop");
urunler.Should().BeInAscendingOrder(u => u.Fiyat);
urunler.Should().AllSatisfy(u => u.Fiyat.Should().BePositive());
urunler.Should().OnlyContain(u => u.Stok >= 0);

// ─── Exception ────────────────────────────────────────────────────
Action islem = () => servis.Ekle(null!);
islem.Should().Throw<ArgumentNullException>()
     .WithMessage("*null*");

Func<Task> asyncIslem = () => servis.OlusturAsync(0, 0);
await asyncIslem.Should().ThrowAsync<ArgumentException>()
                .WithMessage("*pozitif*");

// ─── Tarih ve Zaman ───────────────────────────────────────────────
siparis.OlusturulmaTarihi.Should().BeCloseTo(DateTime.UtcNow, TimeSpan.FromSeconds(5));
siparis.OlusturulmaTarihi.Should().BeAfter(DateTime.UtcNow.AddDays(-1));

BÖLÜM 8 — Gerçek Dünya: Her Şeyi Bir Araya Getirmek

Şimdi öğrendiklerimizin hepsini bir e-ticaret senaryosunda birleştirelim.

Production Kodu

// ── Modeller ──────────────────────────────────────────────────────

public class Urun
{
    public int     Id     { get; set; }
    public string  Ad     { get; set; } = string.Empty;
    public decimal Fiyat  { get; set; }
    public int     Stok   { get; set; }
}

public class Siparis
{
    public int      Id                  { get; set; }
    public int      UrunId              { get; set; }
    public int      Adet                { get; set; }
    public decimal  ToplamFiyat         { get; set; }
    public DateTime OlusturulmaTarihi   { get; set; }
}

// ── Interface'ler ─────────────────────────────────────────────────

public interface IUrunRepository
{
    Task<Urun?> GetirByIdAsync(int id);
}

public interface IStokServis
{
    Task<bool> StokVarMiAsync(int urunId, int adet);
}

public interface IBildirimServis
{
    Task BildirimGonderAsync(string alici, string mesaj);
}

// ── Özel Exception ────────────────────────────────────────────────
public class StokYetersizException : Exception
{
    public StokYetersizException(string message) : base(message) { }
}

// ── Asıl Servis ───────────────────────────────────────────────────

public class SiparisServis
{
    private readonly IUrunRepository    _urunRepository;
    private readonly IStokServis        _stokServis;
    private readonly IBildirimServis    _bildirimServis;

    public SiparisServis(
        IUrunRepository  urunRepository,
        IStokServis      stokServis,
        IBildirimServis  bildirimServis)
    {
        _urunRepository = urunRepository;
        _stokServis     = stokServis;
        _bildirimServis = bildirimServis;
    }

    public async Task<Siparis> OlusturAsync(int productId, int quantity)
    {
        // Ürünü veritabanından getir
        var urun = await _urunRepository.GetirByIdAsync(productId)
            ?? throw new ArgumentException($"Ürün bulunamadı: {productId}");

        // Stok kontrolü yap
        bool stokVar = await _stokServis.StokVarMiAsync(productId, quantity);
        if (!stokVar)
            throw new StokYetersizException($"'{urun.Ad}' için yeterli stok yok.");

        // Siparişi oluştur
        var siparis = new Siparis
        {
            UrunId            = productId,
            Adet              = quantity,
            ToplamFiyat       = urun.Fiyat * quantity,
            OlusturulmaTarihi = DateTime.UtcNow
        };

        // Müşteriye bildirim gönder
        await _bildirimServis.BildirimGonderAsync(
            "musteri@ornek.com",
            $"Siparişiniz alındı: {urun.Ad} x{quantity}"
        );

        return siparis;
    }
}

Test Kodu

public class SiparisServisTests
{
    // ── Tüm testlerin ortak kullandığı mock nesneler ───────────────
    private readonly Mock<IUrunRepository>  _mockUrunRepo   = new();
    private readonly Mock<IStokServis>      _mockStokServis = new();
    private readonly Mock<IBildirimServis>  _mockBildirim   = new();
    private readonly SiparisServis          _servis;

    public SiparisServisTests()
    {
        // Servisi mock nesneleriyle kur
        _servis = new SiparisServis(
            _mockUrunRepo.Object,
            _mockStokServis.Object,
            _mockBildirim.Object
        );
    }

    // ── Senaryo 1: Her şey yolunda ────────────────────────────────

    [Fact(DisplayName = "Geçerli ürün ve yeterli stok → sipariş başarıyla oluşturulur")]
    public async Task Olustur_GecerliUrunVeYeterliStok_BasariliSiparisDonmeli()
    {
        // Hazırla
        var beklenenUrun = new Urun { Id = 1, Ad = "Laptop", Fiyat = 25000 };

        // "GetirByIdAsync(1) çağrılırsa → bu ürünü döndür"
        _mockUrunRepo.Setup(r => r.GetirByIdAsync(1))
                     .ReturnsAsync(beklenenUrun);

        // "StokVarMiAsync(1, 2) çağrılırsa → true döndür (stok var)"
        _mockStokServis.Setup(s => s.StokVarMiAsync(1, 2))
                       .ReturnsAsync(true);

        // "BildirimGonderAsync herhangi bir parametreyle çağrılırsa → başarıyla tamamla"
        _mockBildirim.Setup(b => b.BildirimGonderAsync(It.IsAny<string>(), It.IsAny<string>()))
                     .Returns(Task.CompletedTask);

        // Çalıştır
        var siparis = await _servis.OlusturAsync(productId: 1, quantity: 2);

        // Doğrula
        siparis.Should().NotBeNull();
        siparis.UrunId.Should().Be(1);
        siparis.Adet.Should().Be(2);
        siparis.ToplamFiyat.Should().Be(50000m); // 25000 x 2

        // Bildirim tam olarak 1 kez gönderildi mi?
        _mockBildirim.Verify(
            b => b.BildirimGonderAsync(It.IsAny<string>(), It.IsAny<string>()),
            Times.Once
        );
    }

    // ── Senaryo 2: Ürün bulunamadı ────────────────────────────────

    [Fact(DisplayName = "Var olmayan ürün ID'si → ArgumentException fırlatılır")]
    public async Task Olustur_VarOlmayanUrun_ArgumentExceptionFirlatmali()
    {
        // Hazırla
        // "Herhangi bir ID için → null döndür (ürün yok)"
        _mockUrunRepo.Setup(r => r.GetirByIdAsync(It.IsAny<int>()))
                     .ReturnsAsync((Urun?)null);

        // Çalıştır ve Doğrula
        await Assert.ThrowsAsync<ArgumentException>(
            () => _servis.OlusturAsync(productId: 999, quantity: 1)
        );

        // Ürün bulunamadıysa stok kontrolü yapılmamalı
        _mockStokServis.Verify(
            s => s.StokVarMiAsync(It.IsAny<int>(), It.IsAny<int>()),
            Times.Never
        );

        // Bildirim de gönderilmemeli
        _mockBildirim.Verify(
            b => b.BildirimGonderAsync(It.IsAny<string>(), It.IsAny<string>()),
            Times.Never
        );
    }

    // ── Senaryo 3: Stok yetersiz ──────────────────────────────────

    [Fact(DisplayName = "Stok yetersizse → StokYetersizException fırlatılır")]
    public async Task Olustur_StokYetersiz_StokYetersizExceptionFirlatmali()
    {
        // Hazırla
        _mockUrunRepo.Setup(r => r.GetirByIdAsync(1))
                     .ReturnsAsync(new Urun { Id = 1, Ad = "Laptop", Fiyat = 25000 });

        // "StokVarMiAsync çağrılırsa → false döndür (stok yok)"
        _mockStokServis.Setup(s => s.StokVarMiAsync(1, It.IsAny<int>()))
                       .ReturnsAsync(false);

        // Çalıştır ve Doğrula
        var exception = await Assert.ThrowsAsync<StokYetersizException>(
            () => _servis.OlusturAsync(productId: 1, quantity: 999)
        );

        // Hata mesajının doğru olduğunu kontrol et
        exception.Message.Should().Contain("Laptop");
        exception.Message.Should().Contain("stok");

        // Stok yoksa bildirim gönderilmemeli
        _mockBildirim.Verify(
            b => b.BildirimGonderAsync(It.IsAny<string>(), It.IsAny<string>()),
            Times.Never
        );
    }

    // ── Senaryo 4: Veritabanı hatası ──────────────────────────────

    [Fact(DisplayName = "Veritabanı exception fırlatırsa → üst katmana iletilir")]
    public async Task Olustur_VeritabaniHatasi_ExceptionIletilmeli()
    {
        // Hazırla
        // "GetirByIdAsync çağrılırsa → veritabanı hatası simüle et"
        _mockUrunRepo.Setup(r => r.GetirByIdAsync(It.IsAny<int>()))
                     .ThrowsAsync(new Exception("Veritabanı bağlantısı kesildi."));

        // Çalıştır ve Doğrula
        await Assert.ThrowsAsync<Exception>(
            () => _servis.OlusturAsync(productId: 1, quantity: 1)
        );
    }

    // ── Senaryo 5: Toplam fiyat hesaplama ─────────────────────────

    [Theory(DisplayName = "Farklı adet ve fiyatlarda toplam fiyat doğru hesaplanır")]
    [InlineData(1,  100,     100)]  // 1 adet, 100 TL → 100 TL
    [InlineData(2,  100,     200)]  // 2 adet, 100 TL → 200 TL
    [InlineData(10, 250,    2500)]  // 10 adet, 250 TL → 2500 TL
    [InlineData(3,  33.33, 99.99)]  // Ondalıklı sayı testi
    public async Task Olustur_ToplamFiyat_DogruHesaplanmali(
        int     quantity,
        decimal unitPrice,
        decimal expectedTotal)
    {
        // Hazırla
        _mockUrunRepo.Setup(r => r.GetirByIdAsync(1))
                     .ReturnsAsync(new Urun { Id = 1, Ad = "Test Ürünü", Fiyat = unitPrice });

        _mockStokServis.Setup(s => s.StokVarMiAsync(1, quantity))
                       .ReturnsAsync(true);

        _mockBildirim.Setup(b => b.BildirimGonderAsync(It.IsAny<string>(), It.IsAny<string>()))
                     .Returns(Task.CompletedTask);

        // Çalıştır
        var siparis = await _servis.OlusturAsync(productId: 1, quantity: quantity);

        // Doğrula
        siparis.ToplamFiyat.Should().Be(expectedTotal);
    }

    // ── Senaryo 6: Callback ile eklenen kayıtları takip etme ──────

    [Fact(DisplayName = "Sipariş oluşturulurken bildirim doğru metinle gönderilir")]
    public async Task Olustur_BildirimGonderilirken_DogruMesajIleGonderilmeli()
    {
        // Hazırla
        var gonderilen = new List<(string alici, string mesaj)>();

        _mockUrunRepo.Setup(r => r.GetirByIdAsync(1))
                     .ReturnsAsync(new Urun { Id = 1, Ad = "Laptop", Fiyat = 25000 });

        _mockStokServis.Setup(s => s.StokVarMiAsync(1, 2))
                       .ReturnsAsync(true);

        // Callback: gönderilen her bildirimi kaydet
        _mockBildirim.Setup(b => b.BildirimGonderAsync(It.IsAny<string>(), It.IsAny<string>()))
                     .Callback<string, string>((alici, mesaj) =>
                     {
                         // Her çağrıda alıcıyı ve mesajı kaydet
                         gonderilen.Add((alici, mesaj));
                     })
                     .Returns(Task.CompletedTask);

        // Çalıştır
        await _servis.OlusturAsync(productId: 1, quantity: 2);

        // Doğrula
        gonderilen.Should().HaveCount(1);
        gonderilen[0].mesaj.Should().Contain("Laptop");
        gonderilen[0].mesaj.Should().Contain("2"); // adet bilgisi mesajda olmalı
    }
}

BÖLÜM 9 — Best Practices: İyi Test Kodu Nasıl Yazılır?

Yapılması Gerekenler

  1. Test adı her şeyi anlatmalı

Test başarısız olduğunda, koda bakmadan neyin bozulduğunu anlayabilmelisiniz.

// ❌ Kötü — hiçbir şey söylemiyor
public void Test1() { }
public void UrunTesti() { }

// ✅ İyi — [TestedMethod]_[Scenario]_[ExpectedResult] formatı
public void GetirById_MevcutId_UrunDonmeli() { }
public void Olustur_StokYetersiz_StokYetersizExceptionFirlatmali() { }
  1. Her test tek bir şeyi test etmeli
// ❌ Kötü — bir testte çok fazla şey test ediliyor
[Fact]
public void SiparisServisTesti()
{
    // Hem oluşturma hem stok hem bildirim test ediliyor
    // Biri başarısız olunca hangisinin bozulduğunu anlamak zor
}

// ✅ İyi — her senaryo ayrı bir test
[Fact]
public void Olustur_GecerliUrun_BasariliSiparisDonmeli() { }

[Fact]
public void Olustur_StokYetersiz_ExceptionFirlatmali() { }
  1. Magic number yerine anlamlı değişken kullan
// ❌ Kötü
Assert.Equal(50000m, siparis.ToplamFiyat);

// ✅ İyi
const decimal BirimFiyat     = 25000m;
const int     Adet           = 2;
const decimal BeklenenToplam = BirimFiyat * Adet; // 50000

Assert.Equal(BeklenenToplam, siparis.ToplamFiyat);
  1. AAA bölümlerini yorum satırıyla ayır
[Fact]
public void Hesapla_DogruSonucDonmeli()
{
    // Hazırla
    var servis = new HesaplamaServisi();

    // Çalıştır
    var sonuc = servis.Hesapla(10, 5);

    // Doğrula
    Assert.Equal(50, sonuc);
}

Yapılmaması Gerekenler

  1. Testleri birbirine bağımlı yapma
// ❌ Kötü — Test B, Test A'nın çalışmasına bağlı
public void TestA_UrunEkle() { /* ürün ekler */ }
public void TestB_UrunGetir() { /* TestA'nın eklediği ürünü getirir */ }

// ✅ Her test kendi verisini kendi oluşturmalı
  1. Gerçek dış servis çağırma
// ❌ Kötü — gerçek veritabanı, gerçek API, gerçek e-posta
public void Test()
{
    var db      = new SqlServerContext("Server=prod;..."); // gerçek prod DB!
    var servis  = new EmailServis("smtp.gmail.com");       // gerçek email!
}

// ✅ Her zaman mock kullan
  1. Bir testte çok fazla Assert
// ❌ Kötü — ilk Assert başarısız olunca diğerleri hiç çalışmaz
Assert.Equal("Laptop",  urun.Ad);
Assert.Equal(25000m,    urun.Fiyat);
Assert.Equal(10,        urun.Stok);
Assert.True(urun.AktifMi);
Assert.NotNull(urun.Kategori);

// ✅ FluentAssertions ile daha temiz
urun.Should().NotBeNull();
urun.Ad.Should().Be("Laptop");
urun.Fiyat.Should().Be(25000m);

Testleri Çalıştırma

# Tüm testleri çalıştır
dotnet test

# Belirli bir proje
dotnet test ./MyProject.Tests/

# Trait filtresi ile
dotnet test --filter "Katman=Servis"

# Test adı filtresi
dotnet test --filter "FullyQualifiedName~SiparisServisTests"

# Ayrıntılı çıktı
dotnet test --logger "console;verbosity=detailed"

Özet Tablosu


| Kavram                    | Ne İşe Yarar                             | Kullanım |
|---------------------------|------------------------------------------|------------------------
| [Fact]                    | Tek, sabit bir senaryoyu test eder       | En temel test türü
| [Theory] + [InlineData]   | Aynı testi farklı verilerle çalıştırır   | Çoklu giriş/çıkış 
| [MemberData]              | Test verisini static property'den alır   | Nesne içeren veri 
| [ClassData]               | Test verisini ayrı sınıftan alır         | Büyük ve paylaşılan veri
| Assert.Equal              | Değer eşitliği kontrol eder              | Temel doğrulama 
| Assert.Throws             | Exception fırlatıldığını doğrular        | Hata senaryoları 
| IDisposable               | Her test öncesi/sonrası temizlik         | Kaynak yönetimi 
| IClassFixture<T>          | Pahalı nesneyi sınıf içi paylaştırır     | DB, HTTP client 
| [Trait]                   | Testleri etiketler ve filtreler          | Gruplama 
| Mock<T>                   | Sahte nesne oluşturur                    | Bağımlılık izolasyonu 
| Setup                     | Mock davranışı tanımlar                  | "Şu gelirse bunu yap" 
| It.IsAny<T>()             | Herhangi bir değeri kabul eder           | Esnek eşleştirme 
| It.Is<T>(koşul)           | Koşula uyan değeri kabul eder            | Spesifik eşleştirme 
| Returns                   | Dönüş değeri belirler                    | Sync metotlar 
| ReturnsAsync              | Async dönüş değeri belirler              | Async metotlar 
| Throws / ThrowsAsync      | Hata fırlatmasını simüle eder            | Exception senaryoları 
| Callback                  | Metot çağrıldığında yan etki üretir      | Kayıt, takip 
| Verify                    | Metodun çağrılıp çağrılmadığını doğrular | "Bu çağrıldı mı?" 
| Times.Once                | Tam 1 kez çağrıldığını doğrular          | Sık kullanılan 
| Times.Never               | Hiç çağrılmadığını doğrular              | Hata senaryoları 
| FluentAssertions.Should() | Okunabilir assertion'lar                 | Tüm doğrulamalar

Sonuç

Test yazmak başta ekstra iş gibi görünür. Ama birkaç ay sonra projeye döndüğünüzde, testlerin varlığı size inanılmaz bir özgüven verir. “Bu değişikliği yaparsam ne bozulur?” sorusunun cevabını artık dotnet test verecektir.


메타데이터
post_id
86c8fd1f0403
slug
nette-test-yazmanın-tam-rehberi-xunit-moq-ile-sıfırdan-profesyonel-testler-86c8fd1f0403
url
https://medium.com/@ongguzel/nette-test-yazman%C4%B1n-tam-rehberi-xunit-moq-ile-s%C4%B1f%C4%B1rdan-profesyonel-testler-86c8fd1f0403
canonical_url
https://medium.com/@ongguzel/nette-test-yazman%C4%B1n-tam-rehberi-xunit-moq-ile-s%C4%B1f%C4%B1rdan-profesyonel-testler-86c8fd1f0403
author_url
https://medium.com/@ongguzel
status
ok
fetched_at
2026-07-11 01:01:15