Mocking — Moq, Mock<T>, Setup, Verify, In-memory DB

~25 daq · .NET Backend · Test

Muammodan boshlaylik

BuyurtmaService ni test qilmoqchisiz:

public class BuyurtmaService(IBuyurtmaRepository repo, ITolovProvayder tolov, IEmailService email)
{
    public async Task<int> RasmiylashAsync(BuyurtmaDto dto, CancellationToken ct) { ... }
}

Haqiqiy bog'liqliklar bilan test:

  • Baza kerak (sekin, sozlash murakkab)
  • To'lov provayderi — haqiqiy pul yechiladi
  • Har bir test haqiqiy email yuboradi
  • "To'lov rad etildi" holatini qanday yaratasiz?

Mock nima?

Mock — haqiqiy bog'liqlik o'rniga qo'yiladigan soxta ob'ekt: siz uning qanday javob berishini belgilaysiz va qanday chaqirilganini tekshirasiz.

Tur Vazifasi
Stub Tayyor javob qaytaradi ("to'lov muvaffaqiyatli")
Mock Chaqirilganini tekshirish ("email bir marta yuborildimi?")
Fake Soddalashtirilgan ishlaydigan versiya (xotiradagi repository)

Mock qilish uchun bog'liqlik interfeys orqali kelishi kerak — DI ning asosiy foydalaridan biri (38-dars).


Hayotiy o'xshatma

Kino suratga olish. Qahramon yong'in ichidan chiqadigan sahna — haqiqiy binoni yoqishmaydi. Dekoratsiya va maxsus effektlar: ular aynan kerakli paytda, kerakli tarzda "yonadi". Aktyorning o'yinini tekshirish uchun haqiqiy yong'in shart emas — faqat uning qanday harakat qilishi muhim.


Moq bilan boshlash

dotnet add Dokon.Tests package Moq
using Moq;

public class BuyurtmaServiceTests
{
    private readonly Mock<IBuyurtmaRepository> _repo = new();
    private readonly Mock<ITolovProvayder> _tolov = new();
    private readonly Mock<IEmailService> _email = new();
    private readonly BuyurtmaService _service;

    public BuyurtmaServiceTests()
    {
        _service = new BuyurtmaService(_repo.Object, _tolov.Object, _email.Object);
    }
}

_repo.Object — interfeysni amalga oshirgan soxta ob'ekt.


Setup — javobni belgilash

[Fact]
public async Task Rasmiylash_tolov_muvaffaqiyatli_email_yuboriladi()
{
    // Arrange
    _tolov
        .Setup(t => t.YechAsync(It.IsAny<decimal>(), It.IsAny<CancellationToken>()))
        .ReturnsAsync(new TolovNatija(true, "TX-123"));

    var dto = new BuyurtmaDto(MijozId: 7, Summa: 250_000);

    // Act
    var id = await _service.RasmiylashAsync(dto, CancellationToken.None);

    // Assert
    _repo.Verify(r => r.Qosh(It.Is<Buyurtma>(b => b.MijozId == 7 && b.Summa == 250_000)), Times.Once);
    _email.Verify(e => e.TasdiqYuborAsync(7, It.IsAny<CancellationToken>()), Times.Once);
}

Xato holatini yaratish

[Fact]
public async Task Rasmiylash_tolov_rad_etilsa_buyurtma_saqlanmaydi()
{
    _tolov
        .Setup(t => t.YechAsync(It.IsAny<decimal>(), It.IsAny<CancellationToken>()))
        .ReturnsAsync(new TolovNatija(false, null));

    await Assert.ThrowsAsync<TolovException>(() =>
        _service.RasmiylashAsync(new BuyurtmaDto(7, 250_000), CancellationToken.None));

    _repo.Verify(r => r.Qosh(It.IsAny<Buyurtma>()), Times.Never);
    _email.Verify(e => e.TasdiqYuborAsync(It.IsAny<int>(), It.IsAny<CancellationToken>()), Times.Never);
}

[Fact]
public async Task Rasmiylash_provayder_ishlamasa_xato()
{
    _tolov
        .Setup(t => t.YechAsync(It.IsAny<decimal>(), It.IsAny<CancellationToken>()))
        .ThrowsAsync(new HttpRequestException("timeout"));

    await Assert.ThrowsAsync<HttpRequestException>(() =>
        _service.RasmiylashAsync(new BuyurtmaDto(7, 100), CancellationToken.None));
}

Haqiqiy tizimda yaratish qiyin holatlar (timeout, rad etish) — bir qator bilan.


Argument moslash

It.IsAny<int>()                           // istalgan qiymat
It.Is<int>(x => x > 0)                    // shart bilan
It.IsIn(1, 2, 3)                          // ro'yxatdan
It.IsRegex("^[A-Z]{2}-\\d+
quot;) // satr shabloni _repo.Setup(r => r.OlAsync(5, It.IsAny<CancellationToken>())).ReturnsAsync(new Buyurtma { Id = 5 }); _repo.Setup(r => r.OlAsync(It.Is<int>(id => id != 5), It.IsAny<CancellationToken>())).ReturnsAsync((Buyurtma?)null);

Ketma-ket javoblar va callback

_tolov.SetupSequence(t => t.YechAsync(It.IsAny<decimal>(), It.IsAny<CancellationToken>()))
    .ThrowsAsync(new HttpRequestException())     // 1-urinish — xato
    .ReturnsAsync(new TolovNatija(true, "TX")); // 2-urinish — muvaffaqiyat (retry testi)

Buyurtma? saqlangan = null;
_repo.Setup(r => r.Qosh(It.IsAny<Buyurtma>())).Callback<Buyurtma>(b => saqlangan = b);

Verify — chaqiruvlarni tekshirish

mock.Verify(x => x.Metod(...), Times.Once);
mock.Verify(x => x.Metod(...), Times.Never);
mock.Verify(x => x.Metod(...), Times.Exactly(3));
mock.Verify(x => x.Metod(...), Times.AtLeastOnce);
mock.VerifyNoOtherCalls();

[!TIP] Asosan natijani tekshiring (qaytgan qiymat, holat), Verify ni esa faqat chaqiruvning o'zi muhim natija bo'lganda ishlating — email yuborildimi, to'lov bir marta yechildimi. Har bir ichki chaqiruvni Verify qilgan testlar kodning qanday yozilganiga bog'lanib qoladi va har qanday refactoring da sinadi.


Nimani mock qilmaslik

  • DbContext — mock qilish juda murakkab va noto'g'ri xulq beradi (LINQ bazada boshqacha ishlaydi). O'rniga — repository interfeysi yoki integration test (Testcontainers, 78-dars)
  • O'z domen klasslaringiz (entity, value object) — haqiqiysini ishlating
  • Siz egasi bo'lmagan turlar — HttpClient ni to'g'ridan-to'g'ri emas, o'z interfeysingiz (ITolovProvayder) orqali

[!INFO] Moq ga muqobil — NSubstitute: var tolov = Substitute.For<ITolovProvayder>(); tolov.YechAsync(default, default).ReturnsForAnyArgs(...). Sintaksisi ixchamroq; ikkalasi ham keng ishlatiladi.


Tez-tez uchraydigan xatolar

Setup qilinmagan async metod — Moq sukut bo'yicha bo'sh/default qiymatli Task qaytaradi; natija null bo'lib NullReferenceException ga olib kelishi mumkin. Kerakli metodlarni setup qiling yoki MockBehavior.Strict bilan setup qilinmaganlarni aniqlang.

Klassni (interfeys emas) mock qilish — faqat virtual metodlar almashtiriladi; interfeys ishlating.

Hamma narsani mock qilish — test faqat mock larni tekshiradi, haqiqiy mantiqni emas.


Xulosa

Mock — haqiqiy bog'liqlik (to'lov, email, repository) o'rniga soxta ob'ekt; buning uchun bog'liqliklar interfeys orqali (DI). Moq: new Mock<IT>(), .Object; Setup(...).ReturnsAsync/ThrowsAsync, SetupSequence, Callback; It.IsAny/It.Is.

Verify(..., Times.Once/Never) — muhim yon ta'sirlar uchun; asosan natijani tekshiring. Qiyin holatlar (rad etish, timeout) — oson yaratiladi. DbContext va domen klasslarini mock qilmang. Muqobil — NSubstitute.


Keyingi dars

78-dars: Integration Test — WebApplicationFactory, test database