Ana içeriğe geç

Mocking

Mocking, bağımlılıkları izole ederek birim testlerin güvenilir olmasını sağlar; yanlış mock kullanımı kırılgan ve anlamsız testlere yol açar.


1. Concrete Sınıfı Doğrudan Kullanmak

Yanlış Kullanım: Test içinde gerçek bağımlılıkları kullanmak.

[Fact]
public async Task CreateOrder_SavesOrder()
{
    var context = new AppDbContext(); // Gerçek veritabanı
    var emailService = new SmtpEmailService(); // Gerçek e-posta gönderir!
    var service = new OrderService(context, emailService);

    await service.CreateAsync(new Order());
}

İdeal Kullanım: Moq ile bağımlılıkları mock’layın.

[Fact]
public async Task CreateOrder_SavesOrderAndSendsEmail()
{
    var mockRepo = new Mock<IOrderRepository>();
    var mockEmail = new Mock<IEmailService>();
    var service = new OrderService(mockRepo.Object, mockEmail.Object);

    await service.CreateAsync(new Order { CustomerEmail = "test@test.com" });

    mockRepo.Verify(r => r.AddAsync(It.IsAny<Order>()), Times.Once);
    mockEmail.Verify(e => e.SendAsync("test@test.com", It.IsAny<string>()), Times.Once);
}

2. Aşırı Detaylı Mock Setup

Yanlış Kullanım: İhtiyaç duyulmayan her metodu setup etmek.

[Fact]
public async Task GetOrder_ReturnsOrder()
{
    var mockRepo = new Mock<IOrderRepository>();
    mockRepo.Setup(r => r.GetByIdAsync(It.IsAny<int>())).ReturnsAsync(new Order());
    mockRepo.Setup(r => r.GetAllAsync()).ReturnsAsync(new List<Order>());
    mockRepo.Setup(r => r.AddAsync(It.IsAny<Order>())).Returns(Task.CompletedTask);
    mockRepo.Setup(r => r.UpdateAsync(It.IsAny<Order>())).Returns(Task.CompletedTask);
    mockRepo.Setup(r => r.DeleteAsync(It.IsAny<int>())).Returns(Task.CompletedTask);
    // Sadece GetByIdAsync kullanılıyor, diğerleri gereksiz

    var service = new OrderService(mockRepo.Object);
    var result = await service.GetAsync(1);

    Assert.NotNull(result);
}

İdeal Kullanım: Sadece test edilen senaryoya gerekli setup’ları yapın.

[Fact]
public async Task GetOrder_WithValidId_ReturnsOrder()
{
    var expectedOrder = new Order { Id = 1, TotalPrice = 500 };
    var mockRepo = new Mock<IOrderRepository>();
    mockRepo.Setup(r => r.GetByIdAsync(1)).ReturnsAsync(expectedOrder);

    var service = new OrderService(mockRepo.Object);

    var result = await service.GetAsync(1);

    Assert.Equal(500, result.TotalPrice);
}

3. Implementation Detail Test Etmek

Yanlış Kullanım: İç implementasyonu test edip davranışı test etmemek.

[Fact]
public async Task PlaceOrder_CallsRepositoryAndEmailInCorrectOrder()
{
    var sequence = new List<string>();
    var mockRepo = new Mock<IOrderRepository>();
    mockRepo.Setup(r => r.AddAsync(It.IsAny<Order>()))
        .Callback(() => sequence.Add("repo"));
    var mockEmail = new Mock<IEmailService>();
    mockEmail.Setup(e => e.SendAsync(It.IsAny<string>(), It.IsAny<string>()))
        .Callback(() => sequence.Add("email"));

    var service = new OrderService(mockRepo.Object, mockEmail.Object);
    await service.PlaceAsync(new Order());

    Assert.Equal(new[] { "repo", "email" }, sequence); // Sıra değişirse test kırılır
}

İdeal Kullanım: Davranışı ve sonucu test edin, çağrı sırasını değil.

[Fact]
public async Task PlaceOrder_SavesOrderAndNotifiesCustomer()
{
    var mockRepo = new Mock<IOrderRepository>();
    var mockEmail = new Mock<IEmailService>();
    var service = new OrderService(mockRepo.Object, mockEmail.Object);

    await service.PlaceAsync(new Order { CustomerEmail = "test@test.com" });

    mockRepo.Verify(r => r.AddAsync(It.IsAny<Order>()), Times.Once);
    mockEmail.Verify(e => e.SendAsync("test@test.com", It.IsAny<string>()), Times.Once);
}

4. Mock Yerine Stub Kullanılması Gereken Yerlerde Mock Kullanmak

Yanlış Kullanım: Basit dönüş değerleri için gereksiz mock framework kullanmak.

var mockClock = new Mock<IClock>();
mockClock.Setup(c => c.UtcNow).Returns(new DateTime(2024, 1, 1));

İdeal Kullanım: Basit senaryolar için lightweight stub oluşturun.

public class FakeClock : IClock
{
    public DateTime UtcNow { get; set; }
}

[Fact]
public void IsExpired_WhenPastExpiryDate_ReturnsTrue()
{
    var clock = new FakeClock { UtcNow = new DateTime(2024, 6, 1) };
    var token = new Token { ExpiresAt = new DateTime(2024, 1, 1) };

    Assert.True(token.IsExpired(clock));
}

5. HttpClient’ı Mock’lamak

Yanlış Kullanım: HttpClient’ı doğrudan mock’lamaya çalışmak.

var mockClient = new Mock<HttpClient>(); // HttpClient mock'lanamaz, sealed metotlar var

İdeal Kullanım: MockHttpMessageHandler ile HttpClient’ı test edin.

public class MockHttpMessageHandler : HttpMessageHandler
{
    private readonly HttpStatusCode _statusCode;
    private readonly string _content;

    public MockHttpMessageHandler(HttpStatusCode statusCode, string content)
    {
        _statusCode = statusCode;
        _content = content;
    }

    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken ct)
    {
        return Task.FromResult(new HttpResponseMessage
        {
            StatusCode = _statusCode,
            Content = new StringContent(_content)
        });
    }
}

[Fact]
public async Task GetProduct_ReturnsProductFromApi()
{
    var json = JsonSerializer.Serialize(new Product { Id = 1, Name = "Laptop" });
    var handler = new MockHttpMessageHandler(HttpStatusCode.OK, json);
    var client = new HttpClient(handler) { BaseAddress = new Uri("https://api.test.com/") };
    var service = new ProductApiClient(client);

    var result = await service.GetProductAsync(1);

    Assert.Equal("Laptop", result.Name);
}