Mocking — Moq, Mock<T>, Setup, Verify, In-memory DB
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),
Verifyni esa faqat chaqiruvning o'zi muhim natija bo'lganda ishlating — email yuborildimi, to'lov bir marta yechildimi. Har bir ichki chaqiruvniVerifyqilgan 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 —
HttpClientni 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