C# Mock Framework Kullanımı (Moq, NSubstitute)
C#’ta Moq ve NSubstitute ile mock framework kullanımını öğrenin. Unit testlerde bağımlılıkları izole etme ve test senaryoları.
Gerçek hayattaki birim testlerde, test edilen sınıfın bağımlı olduğu diğer bileşenlerin davranışlarını izole etmek gerekir.
İşte bu noktada mock framework’leri devreye girer.
Moq ve NSubstitute gibi popüler kütüphaneler, sahte nesneler (mock objects) oluşturarak
bağımlılıkların simüle edilmesini sağlar.
Böylece yalnızca test edilmek istenen sınıfın davranışı kontrol edilir.
Mock Nedir?
Mock, bir sınıfın gerçek bağımlılıkları yerine kullanılan sahte nesnedir. Bu sayede test sırasında:
- Gerçek veritabanı veya servis bağlantılarına gerek kalmaz,
- Testler daha hızlı ve izole şekilde çalışır,
- Dış etkenlerden (API, ağ, disk) kaynaklanan hatalar önlenir,
- Belirli metot çağrılarının gerçekleşip gerçekleşmediği doğrulanabilir.
// Örnek senaryo: Email gönderim servisine bağımlı bir kullanıcı kaydı
public interface IEmailServisi
{
void Gonder(string adres, string mesaj);
}
public class KullaniciKaydi
{
private readonly IEmailServisi _emailServisi;
public KullaniciKaydi(IEmailServisi emailServisi)
{
_emailServisi = emailServisi;
}
public void Kaydet(string ad)
{
// Kaydetme işlemleri...
_emailServisi.Gonder("admin@site.com", $"{ad} başarıyla kaydedildi!");
}
}
Bu sınıfı test ederken gerçek e-posta göndermek istemeyiz. Bu durumda mock email servisi kullanırız.
Moq Framework Nedir?
Moq, .NET ekosisteminde en popüler mock framework’lerinden biridir. Fluent API kullanımıyla kolay okunur ve güçlü doğrulama (verification) desteği sunar.
// NuGet üzerinden yükleme:
dotnet add package Moq
using Moq;
using Xunit;
public class KullaniciKaydiTests
{
[Fact]
public void Kaydet_MailGonderilmeli()
{
// Arrange
var mockMail = new Mock<IEmailServisi>();
var kayit = new KullaniciKaydi(mockMail.Object);
// Act
kayit.Kaydet("Ahmet");
// Assert
mockMail.Verify(
m => m.Gonder("admin@site.com", "Ahmet başarıyla kaydedildi!"),
Times.Once); // Metot 1 kez çağrılmalı
}
}
Verify() metodu, belirli bir davranışın gerçekten gerçekleştiğini test eder.
Böylece davranış tabanlı test (behavior verification) yapılabilir.
Mock Davranışlarını Belirleme
Moq ile yalnızca doğrulama değil, aynı zamanda sahte metotların dönüş değerlerini de tanımlayabilirsiniz.
public interface IStokServisi
{
int Getir(string urunKodu);
}
public class UrunSatis
{
private readonly IStokServisi _stok;
public UrunSatis(IStokServisi stok) => _stok = stok;
public bool SatisYap(string urunKodu)
{
int adet = _stok.Getir(urunKodu);
return adet > 0;
}
}
public class UrunSatisTests
{
[Fact]
public void Stokta_Varsa_SatisBasariliOlmali()
{
var mock = new Mock<IStokServisi>();
mock.Setup(s => s.Getir("ABC123")).Returns(10);
var satis = new UrunSatis(mock.Object);
bool sonuc = satis.SatisYap("ABC123");
Assert.True(sonuc);
}
}
Setup() metodu, sahte nesnenin belirli çağrılarda nasıl davranacağını tanımlar.
Returns() ile geri dönecek değer belirlenir.
Callback Kullanımı
Moq’ta Callback() metodu, bir metot çağrıldığında yan etki (side effect) eklemek için kullanılır.
Örneğin, test sırasında log yazmak veya sayacı artırmak gibi.
int sayac = 0;
var mock = new Mock<IEmailServisi>();
mock.Setup(m => m.Gonder(It.IsAny<string>(), It.IsAny<string>()))
.Callback(() => sayac++);
var kayit = new KullaniciKaydi(mock.Object);
kayit.Kaydet("Zeynep");
Assert.Equal(1, sayac);
Bu örnekte, Gonder() her çağrıldığında sayaç artırılır.
Bu yöntem, testlerde olay takibi için çok kullanışlıdır.
It Helper Sınıfları
Moq, parametre kontrolleri için It sınıfını sağlar. Bu sayede yalnızca belirli parametreleri test edebilir veya wildcard kullanabilirsiniz.
It.IsAny<T>()– Herhangi bir değer kabul eder.It.Is<T>(predicate)– Koşula uyan değerleri test eder.It.IsInRange(min, max, Range.Inclusive)– Değer aralıklarını test eder.
mock.Verify(m =>
m.Gonder(It.IsAny<string>(),
It.Is<string>(msg => msg.Contains("Ahmet"))),
Times.Once);
NSubstitute Framework Nedir?
NSubstitute, Moq’a benzer şekilde mock oluşturmayı sağlayan sade bir API sunar. Özellikle okunabilirliği yüksektir ve “Arrange–Act–Assert” düzenini doğal biçimde destekler.
// NuGet üzerinden yükleme:
dotnet add package NSubstitute
using NSubstitute;
using Xunit;
public class KullaniciKaydiTests
{
[Fact]
public void Kaydet_MailGonderilmeli()
{
var email = Substitute.For<IEmailServisi>();
var kayit = new KullaniciKaydi(email);
kayit.Kaydet("Mehmet");
email.Received(1).Gonder("admin@site.com", "Mehmet başarıyla kaydedildi!");
}
}
Received() metodu, metot çağrısının gerçekleşip gerçekleşmediğini kontrol eder.
Syntax olarak daha doğal bir dil (okunabilir testler) sağlar.
Davranış Tanımlama (Returns ve When)
NSubstitute’ta Returns() ve When() metotlarıyla mock davranışları tanımlanabilir.
var stok = Substitute.For<IStokServisi>();
stok.Getir("P001").Returns(5);
var satis = new UrunSatis(stok);
bool sonuc = satis.SatisYap("P001");
Assert.True(sonuc);
// Çağrı sırasında yan etki ekleme:
stok.When(s => s.Getir("P001"))
.Do(_ => Console.WriteLine("Stok servisi çağrıldı."));
When(...).Do(...) yapısı, belirli bir çağrı gerçekleştiğinde özel eylemler tanımlamak için idealdir.
Moq vs NSubstitute Karşılaştırması
| Özellik | Moq | NSubstitute |
|---|---|---|
| Kullanım Stili | Fluent API (Setup/Verify) | Doğal dil (Returns/Received) |
| Okunabilirlik | Yüksek | Daha yüksek |
| Performans | Biraz daha hızlı | Ortalama |
| Callback Desteği | Var (.Callback()) | Var (.Do()) |
| Popülerlik | Çok yaygın (xUnit ile sık kullanılır) | Daha az ama modern syntax |
Gerçek Hayat Örneği: Sipariş Servisi Testi
Aşağıdaki örnek, hem Moq hem de NSubstitute ile servis bağımlılığının test edildiği bir senaryoyu gösterir.
public interface ISiparisServisi
{
void Kaydet(string urun);
}
public class EPostaServisi
{
public virtual void Gonder(string mesaj)
{
Console.WriteLine($"Mail gönderildi: {mesaj}");
}
}
public class SiparisIslemi
{
private readonly ISiparisServisi _siparis;
private readonly EPostaServisi _posta;
public SiparisIslemi(ISiparisServisi siparis, EPostaServisi posta)
{
_siparis = siparis;
_posta = posta;
}
public void IslemiTamamla(string urun)
{
_siparis.Kaydet(urun);
_posta.Gonder($"{urun} siparişi tamamlandı!");
}
}
// Moq ile test
var siparisMock = new Mock<ISiparisServisi>();
var postaMock = new Mock<EPostaServisi>();
var islem = new SiparisIslemi(siparisMock.Object, postaMock.Object);
islem.IslemiTamamla("Laptop");
siparisMock.Verify(s => s.Kaydet("Laptop"), Times.Once);
postaMock.Verify(p => p.Gonder("Laptop siparişi tamamlandı!"), Times.Once);
// NSubstitute ile test
var siparis = Substitute.For<ISiparisServisi>();
var posta = Substitute.For<EPostaServisi>();
var islem2 = new SiparisIslemi(siparis, posta);
islem2.IslemiTamamla("Laptop");
siparis.Received(1).Kaydet("Laptop");
posta.Received(1).Gonder("Laptop siparişi tamamlandı!");
Performans ve En İyi Uygulamalar
- Mock nesnelerini yalnızca dış bağımlılıklar için kullanın (veritabanı, servis, API, dosya erişimi vb.).
- İş mantığını test ederken real class kullanın, mock yalnızca dış servisleri temsil etsin.
- Gereksiz doğrulamalardan kaçının — testler yalın olmalı.
- Birim testlerde
Arrange – Act – Assertdüzenini uygulayın. - Mock yerine Fake veya Stub tercih edilmesi gereken yerleri ayırt edin.
TL;DR
- Mock Framework’leri, testlerde bağımlılıkları izole etmek için sahte nesneler oluşturur.
- Moq: Fluent API yapısında, popüler ve güçlü doğrulama desteği sunar.
- NSubstitute: Daha okunabilir syntax ve doğal akış sağlar.
Setup()veReturns()→ Davranış tanımı yapar.Verify()/Received()→ Çağrı doğrulaması yapar.- Mock’lar gerçek sistemlerle etkileşmeden yalnızca hedef sınıfı test etmeye odaklanır.
İlişkili Makaleler
C# Delegates ve Events
C#’ta delegate ve event kavramlarını öğrenin. Olay tabanlı programlama, callback mantığı ve kullanım senaryoları örneklerle anlatılıyor.
C# Dependency Injection Temelleri
C#’ta Dependency Injection kavramını öğrenin. Bağımlılıkların yönetimi, gevşek bağlılık ve test edilebilirlik örneklerle anlatılıyor.
C# Unit Test Yazımı (xUnit, NUnit, MSTest)
C#’ta unit test yazmayı öğrenin. xUnit, NUnit ve MSTest ile test senaryoları oluşturarak güvenilir yazılım geliştirin.