Mundarija (29)
- Bu darsda
- 1. Nega bu kerak?
- 2. Test moduli: createTestingModule → compile → get
- 2.1 Shablondagi birinchi test
- 2.2 Nimani sinaymiz, nimani almashtiramiz
- 3. JS modelida test moduli
- 4. VazifalarService ni soxta ombor bilan sinash
- 4.1 Soxta ombor: vi.fn() va Proxy
- 4.2 Topilma: then tuzog'i
- 4.3 Test moduli yordamchisi
- 4.4 Uchta xizmat testi
- 5. Bog'liqlik unutilsa — haqiqiy xabar
- 6. overrideProvider — tayyor ro'yxatda almashtirish
- 7. Guard, pipe va interceptor — alohida
- 7.1 Soxta ExecutionContext
- 7.2 Guard testi
- 7.3 Pipe testi
- 7.4 Interceptor testi
- 8. Natija: biz ishga tushirdik
- 9. Vitest yoki Jest — Nest 12 da
- 10. Ko'p uchraydigan xatolar
- 11. Mashqlar
- 1-mashq (oson): zanjirni to'ldiring
- 2-mashq (o'rta): soxta funksiya yozing
- 3-mashq (qiyin): guard'ni soxta kontekst bilan sinash
- 12. Loyiha qadami: test/birlik.test.ts
- 13. Real ishda
- Xulosa
- Manbalar
NestJS unit testlari: Test.createTestingModule, soxta provider va overrideProvider
Qisqacha: Nest'da unit test — bitta klassni haqiqiy DI orqali, lekin soxta bog'liqliklar bilan yaratib tekshirish.
Test.createTestingModule({ providers: [...] })kichik test modulini yasaydi,.compile()uni yig'adi,.get(Klass)tayyor nusxani beradi. Bog'liqlik o'rniga{ provide: TOKEN, useValue: soxta }yoziladi; tayyor modulda esaoverrideProvider(TOKEN).useValue(...)almashtiradi. Guard, pipe va interceptor — oddiy klass: ularninewbilan yaratib, soxtaExecutionContextberib sinaysiz.
Bu darsda
Test.createTestingModule→compile→getzanjirini va u Nest konteyneridan nimasi bilan farq qilishini tushuntirasiz.- Xizmatni soxta ombor bilan sinaysiz va
vi.fn()chaqiruvlarini tekshirasiz. useValuevaoverrideProviderni qachon ishlatishni bilasiz, Proxy soxtasiningthentuzog'ini tanib olasiz.- Guard, pipe va interceptor'ni HTTP serversiz, alohida sinaysiz.
- Nest 12 da nega sukut test vositasi Vitest ekanini va Jest bilan o'lchov natijasini bilasiz.
Oldin bilishingiz kerak: Mock, stub, spy va fake, Vitest II: mock, spy, taymerlar va coverage, DI va IoC konteyner, Custom provider'lar, Guard'lar, Interceptor'lar va RxJS.
O'tgan darsda modular monolit haqida gaplashdik: modul chegaralari qat'iy bo'lsa, har bo'lakni alohida almashtirish mumkin. Testda ham xuddi shu g'oya ishlaydi — bitta klassni olib, atrofidagilarni soxtasi bilan almashtiramiz.
1. Nega bu kerak?
25–27-qismlarda vazifalar-api ning 130 ga yaqin testi bor edi. Ularning ko'pi HTTP integratsiya testi: haqiqiy so'rov yuboriladi, haqiqiy Express (endi Nest) uni qayta ishlaydi, javob tekshiriladi (Backend test strategiyasi). Bu to'g'ri tanlov — foydalanuvchi aynan shu shartnomani ko'radi.
Lekin ba'zi qoidalar HTTP orqali sinash uchun noqulay. Masalan, VazifalarService ichida qoida bor: vazifa false dan true ga o'tgandagina "vazifa bajarildi" hodisasi chiqadi. Ikkinchi marta true yuborilsa — hodisa chiqmaydi. Buni HTTP'dan tekshirish uchun webhook qabul qiladigan server kerak bo'ladi.
Yana bir misol — guard. JsonTanaGuard noto'g'ri Content-Type ga 415 beradi. U to'g'ri ishlashini ko'rish uchun butun ilovani ko'tarish shart emas: guard — oddiy klass, uning bitta metodi bor.
O'xshatish: avtomobil zavodida dvigatelni mashinaga o'rnatishdan oldin stendda sinashadi. Stend dvigatelga yoqilg'i, havo va elektr beradi — haqiqiy mashina bo'lmasa ham. Unit test — shu stend. Nest DI konteyneri esa stendni yasashni osonlashtiradi: bog'liqliklarni klass o'zi yaratmaydi, tashqaridan oladi (DI va IoC konteyner). Demak, testda ularni soxtasi bilan almashtirish mumkin.
2. Test moduli: createTestingModule → compile → get
2.1 Shablondagi birinchi test
nest new yaratgan loyihada bitta unit test bor edi (Nest CLI va loyiha tuzilmasi). Uni endi to'liq o'qiymiz:
import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller.js';
import { AppService } from './app.service.js';
describe('AppController', () => {
let appController: AppController;
beforeEach(async () => {
const app: TestingModule = await Test.createTestingModule({
controllers: [AppController],
providers: [AppService],
}).compile();
appController = app.get<AppController>(AppController);
});
it('should return "Hello World!"', () => {
expect(appController.getHello()).toBe('Hello World!');
});
});Uch qadam bor:
| Qadam | Nima qiladi | Ilovadagi o'xshashi |
|---|---|---|
Test.createTestingModule({...}) |
test moduli tavsifi — @Module dagi maydonlar |
@Module({...}) |
.compile() |
DI konteynerni yig'adi, nusxalarni yaratadi | NestFactory.create |
.get(Klass) |
tayyor nusxani beradi | konteyner ichidagi nusxa |
@nestjs/testing paketi @nestjs/core bilan bir versiyada — kanonda 12.2.0. Test moduli HTTP server ochmaydi, port olmaydi, middleware ulanmaydi. U faqat DI qismini quradi. Shuning uchun tez ishlaydi.
Maslahat:
compile()Promise qaytaradi —awaitni unutmang. Unutsangiz,.getmetodi Promise'da yo'q degan xato olasiz.
2.2 Nimani sinaymiz, nimani almashtiramiz
Unit testda bitta klass haqiqiy bo'ladi — siz sinayotgan klass. Uning bog'liqliklari — soxta (mock, Mock, stub, spy va fake). Bizning asosiy misol — VazifalarService. Unga uchta narsa kerak: ombor (VAZIFA_OMBORI token bilan), log (LOG) va hodisalar (EventEmitter2):
flowchart LR
T["Test.createTestingModule"] --> X["VazifalarService (haqiqiy)"]
O["VAZIFA_OMBORI: soxta ombor"] --> X
L["LOG: jim log"] --> X
E["EventEmitter2: haqiqiy, bo'sh"] --> X
X --> N["xizmat.yarat() natijasi"]
O -. "vi.fn() chaqiruvlari" .-> K["expect(...)"]
E -. "hodisa tinglovchisi" .-> KDiagrammaga qarang: o'rtadagi xizmat — haqiqiy kod. Chapdagi uchta bog'liqlik — test bergan qiymatlar. Pastdagi punktir chiziqlar — test kuzatadigan joylar: ombor qanday chaqirildi, qaysi hodisa chiqdi.
EventEmitter2 ni soxtalashtirmadik — haqiqiy, lekin yangi va bo'sh nusxa berdik. Bu ham to'g'ri yo'l: obyekt sodda va tez bo'lsa, haqiqiysi soxtasidan yaxshiroq. Ombor esa bazaga boradi — uni albatta almashtiramiz.
Tekshirib ko'ring: Test moduli
NestFactory.createdan nimasi bilan farq qiladi?
Javob
Test moduli faqat DI konteynerni quradi: provider va controller nusxalari. HTTP server, middleware, global pipe va filtrlar yo'q, port ochilmaydi. Shuning uchun u tez, va unda sinalayotgan klass faqat o'z bog'liqliklariga tayanadi. HTTP qatlami ham kerak bo'lsa — keyingi darsdagi e2e test.
3. JS modelida test moduli
Test moduli ichida nima bo'layotganini kichik modelda takrorlaymiz. Bu blok brauzerda ishlaydi, dekorator va paket yo'q. Klass o'ziga kerak tokenlarni static kerak ro'yxatida aytadi — Nest buni @Inject va emitDecoratorMetadata dan oladi (Reflector va metadata):
const OMBOR = "OMBOR";
class VazifalarXizmati {
static kerak = [OMBOR];
constructor(ombor) {
this.ombor = ombor;
}
async ol(id) {
const v = await this.ombor.ol(id);
if (!v) throw new Error(`Vazifa ${id} topilmadi (404)`);
return v;
}
}
// Test moduli modeli: klass yoki { provide, useValue }
function testModuli(providers) {
const kalit = (p) => p.provide ?? p;
const royxat = new Map(providers.map((p) => [kalit(p), p]));
const nusxalar = new Map();
function get(token) {
if (nusxalar.has(token)) return nusxalar.get(token);
const p = royxat.get(token);
if ("useValue" in p) return p.useValue;
const args = p.kerak.map((dep, i) => {
if (!royxat.has(dep)) {
throw new Error(`${p.name} ning ${i}-argumenti (${dep}) ` +
"test modulida yo'q");
}
return get(dep);
});
const nusxa = new p(...args);
nusxalar.set(token, nusxa);
return nusxa;
}
return {
overrideProvider(token) {
return {
useValue(qiymat) {
royxat.set(token, { provide: token, useValue: qiymat });
},
};
},
compile() {
for (const token of royxat.keys()) get(token);
return { get };
},
};
}
// 1) bog'liqlik unutildi
try {
testModuli([VazifalarXizmati]).compile();
} catch (e) {
console.log(e.message);
}
// 2) haqiqiy xizmat + soxta ombor
const chaqiruvlar = [];
const soxtaOmbor = {
ol: async (id) => {
chaqiruvlar.push(id);
return id === 1 ? { id: 1, matn: "Non olish" } : undefined;
},
};
const m = testModuli([
VazifalarXizmati,
{ provide: OMBOR, useValue: soxtaOmbor },
]).compile();
const xizmat = m.get(VazifalarXizmati);
console.log(await xizmat.ol(1));
try {
await xizmat.ol(5);
} catch (e) {
console.log(e.message);
}
console.log("ombor chaqiruvlari:", chaqiruvlar);
// 3) overrideProvider — compile'dan oldin almashtiramiz
const t = testModuli([
VazifalarXizmati,
{ provide: OMBOR, useValue: null },
]);
t.overrideProvider(OMBOR).useValue({ ol: async () => ({ id: 9 }) });
console.log(await t.compile().get(VazifalarXizmati).ol(9));Konsolda:
VazifalarXizmati ning 0-argumenti (OMBOR) test modulida yo'q
{ id: 1, matn: 'Non olish' }
Vazifa 5 topilmadi (404)
ombor chaqiruvlari: [ 1, 5 ]
{ id: 9 }Uch holatga qarang:
- Ombor ro'yxatda yo'q —
compile()darhol yiqildi. Nest ham shunday qiladi, birinchi testgacha. - Soxta ombor berildi — xizmatning haqiqiy kodi ishladi: 1 topildi, 5 uchun 404 xatosi.
chaqiruvlarmassivi — qo'lda yozilgan "josus" (spy): ombor qaysi argument bilan chaqirilganini eslab qoldi. overrideProvider— ro'yxatdagi yozuvnicompile()dan oldin almashtirdi.
Model soddalashtirilgan: unda scope, async fabrika va modul chegarasi yo'q. Lekin asosiy g'oya aynan shu — konteyner tokenga qarab qiymat beradi, test esa qiymatni o'zi tanlaydi.
4. VazifalarService ni soxta ombor bilan sinash
4.1 Soxta ombor: vi.fn() va Proxy
Vitest'dagi vi.fn() ni 22-qismda o'rgangansiz (Vitest II): u chaqiruvlarni eslab qoladigan soxta funksiya. mockResolvedValue(x) — har chaqiruvda Promise.resolve(x) qaytaradi, mockResolvedValueOnce(x) — faqat navbatdagi bitta chaqiruvda.
VazifaOmbori interfeysida o'nga yaqin metod bor. Har testda hammasini yozish og'ir. Kanon kichik yordamchi yozdi — Proxy asosidagi soxta ombor. Proxy — obyekt o'rniga turib, har xususiyat so'rovini ushlaydigan o'rovchi. Test bergan metodni qaytaradi, qolgan har qanday metodga "kutilmagan chaqiruv" xatosini tashlaydi:
type Soxtalar = Partial<
Record<keyof VazifaOmbori, ReturnType<typeof vi.fn>>
>;
/** VazifaOmbori soxtasi: faqat test kerak qilgan metodlar
* (qolgani chaqirilsa — xato) */
function soxtaOmbor(ust: Soxtalar): VazifaOmbori {
// "then" — Nest useValue qiymati Promise emasligini tekshiradi
// (thenable) — undefined qaytsin
return new Proxy({} as VazifaOmbori, {
get: (_t, k: string) => {
if (k === "then") return undefined;
return (
ust[k as keyof VazifaOmbori] ??
(() => {
throw new Error(`kutilmagan chaqiruv: ombor.${k}`);
})
);
},
});
}Nega "kutilmagan chaqiruv" xatosi foydali? Xizmat kodini kimdir o'zgartirib, ombordan yangi metod chaqira boshlasa, test darhol aytadi. Jim undefined qaytarganda esa test noto'g'ri sabab bilan o'tib ketishi mumkin edi.
4.2 Topilma: then tuzog'i
Izohdagi "then" qatori — kanon ko'chish paytida topgan haqiqiy muammo. Shu qatorsiz Proxy'ni sinab ko'rdik (biz ishga tushirdik, NestJS 12.2.0, Vitest 5.0.5):
XATO: kutilmagan chaqiruv: ombor.thenTest hali bitta ham metod chaqirmagan, lekin compile() yiqildi. Sabab: Nest useValue ga berilgan qiymatni tekshiradi — u Promise emasmi? JavaScript'da "Promise'ga o'xshash" (thenable) obyektni bilish usuli bitta: qiymat.then funksiyami. Proxy esa har qanday nom uchun funksiya qaytaradi — then uchun ham. Nest uni Promise deb chaqirdi, funksiya xato tashladi.
Saboq kengroq: Proxy soxtasi then ga undefined qaytarsin. Bu faqat Nest'da emas — await soxta yozilgan har joyda shu tuzoq bor.
4.3 Test moduli yordamchisi
Har testda bir xil to'rt provider yozilmasligi uchun kanon yordamchi funksiya qildi:
async function xizmatModuli(ombor: VazifaOmbori) {
const m = await Test.createTestingModule({
providers: [
VazifalarService,
{ provide: VAZIFA_OMBORI, useValue: ombor },
{ provide: LOG, useValue: logYarat("silent") },
{ provide: EventEmitter2, useValue: new EventEmitter2() },
],
}).compile();
return {
xizmat: m.get(VazifalarService),
hodisalar: m.get(EventEmitter2),
};
}VazifalarService — haqiqiy klass, ro'yxatda o'zi turibdi. Qolganlari useValue bilan berilgan (Custom provider'lar). logYarat("silent") — haqiqiy pino logger, lekin hech narsa chiqarmaydi: test natijasi log bilan to'lib ketmaydi.
4.4 Uchta xizmat testi
describe("VazifalarService (mock ombor, DI)", () => {
test("yarat: ombor chaqiriladi, 'vazifa.yaratildi' chiqadi",
async () => {
const yarat = vi.fn().mockResolvedValue(v({ id: 7 }));
const soni = vi.fn().mockResolvedValue(0);
const { xizmat, hodisalar } = await xizmatModuli(
soxtaOmbor({ soni, yarat }),
);
const tinglovchi = vi.fn();
hodisalar.on(HODISA.yaratildi, tinglovchi);
await expect(xizmat.yarat({ matn: "Non" }))
.resolves.toMatchObject({ id: 7 });
expect(yarat).toHaveBeenCalledWith({ matn: "Non" });
expect(tinglovchi).toHaveBeenCalledWith(
expect.objectContaining({ id: 7 }),
);
});
test("ol: ombor undefined → TopilmadiXatosi (404)", async () => {
const ol = vi.fn().mockResolvedValue(undefined);
const { xizmat } = await xizmatModuli(soxtaOmbor({ ol }));
await expect(xizmat.ol(5))
.rejects.toBeInstanceOf(TopilmadiXatosi);
});
test("yangila: faqat false → true da 'vazifa.bajarildi'",
async () => {
const ol = vi.fn()
.mockResolvedValueOnce(v({ bajarildi: false }))
.mockResolvedValueOnce(v({ bajarildi: true }));
const yangila = vi.fn().mockResolvedValue({
tur: "ok",
qiymat: v({ bajarildi: true, versiya: 2 }),
});
const { xizmat, hodisalar } = await xizmatModuli(
soxtaOmbor({ ol, yangila }),
);
const bajarildi = vi.fn();
hodisalar.on(HODISA.bajarildi, bajarildi);
await xizmat.yangila(1, { bajarildi: true });
await xizmat.yangila(1, { bajarildi: true });
expect(bajarildi).toHaveBeenCalledTimes(1);
});
});Har testni qisqacha o'qiymiz:
yarat— xizmat avvalsoni()ni so'raydi (ro'yxat 1000 ta bilan cheklangan), keyinyarat()ni. Soxtasoni0 qaytaradi. Test uch narsani tekshiradi: natija, ombor qanday argument bilan chaqirilgani va hodisa chiqqani.ol— omborundefinedqaytarsa, xizmat 404 xatosini (TopilmadiXatosi) tashlashi kerak. HTTP yo'q — faqat xato klassi tekshiriladi. 404 statusga aylanishi — filtrning ishi (Exception filter'lar), bu boshqa test.yangila— eng qiziq qoida.olikki marta chaqiriladi: birinchisida eski holatfalse, ikkinchisidatrue.mockResolvedValueOncezanjiri shu ketma-ketlikni beradi. Ikkiyangiladan keyinbajarildihodisasi faqat bir marta chiqqan bo'lishi kerak.
v() — kichik yordamchi: to'liq Vazifa obyektini yasaydi, test faqat o'zgargan maydonni beradi (v({ id: 7 })). Bu — 27-qismdagi factory g'oyasining eng sodda shakli.
Tekshirib ko'ring:
yangilatestidamockResolvedValueOnceo'rnigamockResolvedValue(v({ bajarildi: false }))yozilsa, test nima ko'rsatadi?
Javob
Ikkala ol chaqiruvi ham false qaytaradi. Xizmat ikkala safar ham "false → true o'tish bo'ldi" deb o'ylaydi va hodisani ikki marta chiqaradi. toHaveBeenCalledTimes(1) yiqiladi. Soxtaning javoblari ketma-ketligi haqiqiy hayotni takrorlashi kerak: birinchi yangila dan keyin bazada true turadi.
5. Bog'liqlik unutilsa — haqiqiy xabar
Test modulida LOG ni yozishni unutdik (biz ishga tushirdik):
Nest can't resolve dependencies of the VazifalarService (Symbol(VAZIFA_OMBORI), ?, EventEmitter). Please make sure that the argument Symbol(LOG) at index [1] is available in the RootTestModule module.
Potential solutions:
- Is RootTestModule a valid NestJS module?
- If Symbol(LOG) is a provider, is it part of the current RootTestModule?
- If Symbol(LOG) is exported from a separate @Module, is that module imported within RootTestModule?
@Module({
imports: [ /* the Module containing Symbol(LOG) */ ]
})
For more common dependency resolution issues, see: https://docs.nestjs.com/faq/common-errorsTarjimasi: "Nest VazifalarService ning bog'liqliklarini topa olmadi. RootTestModule modulida 1-indeksdagi Symbol(LOG) argumenti mavjudligiga ishonch hosil qiling."
Bu xabarni Modullar darsida o'qishni o'rgangansiz. Ikki yangi narsa bor:
RootTestModule—createTestingModuleyaratgan yashirin modul nomi. Xabarda shu nomni ko'rsangiz, muammo test sozlamasida, ilova kodida emas.Symbol(LOG)— token klass emas,Symbolbo'lgani uchun shunday ko'rinadi.(Symbol(VAZIFA_OMBORI), ?, EventEmitter)— uchta argument, ikkinchisi topilmadi.
6. overrideProvider — tayyor ro'yxatda almashtirish
useValue ni ro'yxatga o'zingiz yozasiz. Lekin ba'zan ro'yxat sizniki emas — masalan, butun VazifalarModule ni import qilasiz va uning ichidagi bitta provider'ni almashtirmoqchisiz. Bunga overrideProvider bor:
test("overrideProvider: XABARCHI almashtirildi, hodisa → xabarchi",
async () => {
const xabarchi = vi.fn();
const m = await Test.createTestingModule({
providers: [
WebhookTinglovchi,
{ provide: XABARCHI, useValue: null },
],
})
.overrideProvider(XABARCHI).useValue(xabarchi)
.compile();
m.get(WebhookTinglovchi).bajarildi(v({ bajarildi: true }));
expect(xabarchi).toHaveBeenCalledWith(
"vazifa.bajarildi",
expect.objectContaining({ id: 1 }),
);
});WebhookTinglovchi — Hodisalar darsidagi tinglovchi: "vazifa bajarildi" hodisasini olib, webhook xabarchisiga beradi. Ro'yxatda XABARCHI null (ilovada webhook sozlanmagan holat). overrideProvider(XABARCHI).useValue(xabarchi) uni compile() dan oldin vi.fn() bilan almashtirdi.
overrideProvider dan keyin to'rt usul bor: useValue, useClass, useFactory va useExisting. Ma'nosi providers dagi bilan bir xil. Xuddi shunday overrideGuard, overridePipe, overrideInterceptor, overrideFilter ham bor — global yoki @UseGuards bilan ulangan enhancer'ni almashtirish uchun. Ularni keyingi darsda e2e testda ishlatamiz.
| Usul | Qachon |
|---|---|
providers da useValue |
ro'yxatni o'zingiz yozasiz (eng ko'p uchraydi) |
overrideProvider |
modulni butunlay import qildingiz, ichidan bittasini almashtirasiz |
overrideGuard va boshqalar |
enhancer'ni o'chirish yoki soxtalash (e2e) |
Diqqat:
overrideProviderfaqatcompile()dan oldin ishlaydi. Tayyor modulda provider allaqachon yaratilgan — keyin almashtirib bo'lmaydi.
7. Guard, pipe va interceptor — alohida
Ilovada bu uchtasi Request lifecycle zanjirining bo'g'inlari: so'rov middleware → guard → interceptor → pipe → handler → interceptor → xato filtri tartibida o'tadi. Unit testda zanjir yo'q — har bo'g'inni yolg'iz chaqiramiz. Shuning uchun Nest normalda o'zi beradigan narsani test qo'lda beradi:
| Bo'g'in | Ilovada Nest beradi | Testda biz beramiz |
|---|---|---|
| guard | ExecutionContext, metama'lumot |
soxta kontekst, Reflect.defineMetadata |
| pipe | qiymat va ArgumentMetadata |
ikkalasini qo'lda |
| interceptor | kontekst va keyingi bo'g'in (CallHandler) |
soxta kontekst va of(...) |
Zanjirning o'zi — tartib, global ulanish, filtr xatoni javobga aylantirishi — keyingi darsdagi e2e testning ishi.
7.1 Soxta ExecutionContext
Guard va interceptor ExecutionContext oladi (Guard'lar). Haqiqiy kontekstni Nest so'rov paytida yasaydi. Testda bizga uning faqat bir nechta metodi kerak — shularni oddiy obyekt bilan beramiz:
/** HTTP'siz ExecutionContext soxtasi (guard/interceptor uchun) */
function kontekst(
req: object,
handler: () => unknown,
res: object = {},
): ExecutionContext {
return {
getHandler: () => handler,
getClass: () => Object,
switchToHttp: () => ({
getRequest: () => req,
getResponse: () => res,
getNext: () => undefined,
}),
} as unknown as ExecutionContext;
}as unknown as ExecutionContext — TypeScript'ga "bu obyekt to'liq emas, bilib turibman" degani. Interfeysning qolgan metodlari (getArgs, switchToRpc …) yo'q: ularni chaqirsa, test darhol yiqiladi va qaysi metod kerakligini ko'rsatadi.
7.2 Guard testi
test("JsonTanaGuard: belgisiz — o'tadi; belgili + text/plain — 415",
() => {
const guard = new JsonTanaGuard(new Reflector());
const belgisiz = () => 1;
const belgili = () => 2;
// @JsonTana() xuddi shu metama'lumotni yozadi
Reflect.defineMetadata("vazifalar:json-tana", true, belgili);
const req = { is: () => false };
expect(guard.canActivate(kontekst(req, belgisiz))).toBe(true);
expect(() => guard.canActivate(kontekst(req, belgili)))
.toThrow(expect.objectContaining({ holat: 415 }));
});Bu yerda test moduli umuman yo'q: new JsonTanaGuard(new Reflector()). Guard'ning yagona bog'liqligi — Reflector, uni o'zimiz yaratdik. Bog'liqlik kam bo'lsa, new — eng sodda yo'l.
Reflect.defineMetadata — @JsonTana() dekoratori qiladigan ishni qo'lda bajaradi: handler funksiyasiga belgi qo'yadi (Custom dekoratorlar). req.is — Express'ning "so'rov shu turdami" metodi; soxtasi doim false — ya'ni so'rov JSON emas.
7.3 Pipe testi
test("SxemaPipe: body → 422, param → 400, sxemasiz — o'zgarishsiz",
async () => {
const pipe = new SxemaPipe();
const sxema = z.strictObject({
matn: z.string().min(1, { error: "bo'sh" }),
});
const body = { type: "body", schema: sxema } as ArgumentMetadata;
await expect(pipe.transform({ matn: "" }, body)).rejects
.toSatisfy((x) => x instanceof ValidatsiyaXatosi &&
x.xatolar?.[0]?.joy === "body.matn");
const param = {
type: "param",
data: "id",
schema: z.string().regex(/^\d+$/),
} as ArgumentMetadata;
await expect(pipe.transform("abc", param)).rejects
.toSatisfy((x) => x instanceof ParametrXatosi &&
x.xatolar?.[0]?.joy === "params.id");
const sxemasiz = { type: "body" } as ArgumentMetadata;
await expect(pipe.transform({ x: 1 }, sxemasiz))
.resolves.toEqual({ x: 1 });
});Pipe — transform(qiymat, metadata) metodi bor klass (Pipe'lar). ArgumentMetadata ni Nest dekoratorlardan yig'adi: @Body({ schema }) → { type: "body", schema }. Testda uni qo'lda yozamiz. Uch holat — kanonning uch qoidasi: tana xatosi 422, yo'l parametri xatosi 400, sxemasiz parametr tegilmaydi (Zod va Standard Schema).
7.4 Interceptor testi
test("VazifaSarlavhalariInterceptor: ETag va Last-Modified",
async () => {
const sarlavhalar: Record<string, string> = {};
const res = {
set: (k: string, q: string) => {
sarlavhalar[k] = q;
},
};
const keyingi: CallHandler<Vazifa> = {
handle: () => of(v({ versiya: 4 })),
};
const ic = new VazifaSarlavhalariInterceptor();
await lastValueFrom(
ic.intercept(kontekst({}, () => 0, res), keyingi),
);
expect(sarlavhalar).toEqual({
ETag: '"4"',
"Last-Modified": "Wed, 28 Oct 2026 00:00:00 GMT",
});
});Interceptor handler'ni CallHandler orqali chaqiradi va Observable qaytaradi (Interceptor'lar va RxJS). Testda handler — of(...): bitta qiymat chiqarib tugaydigan Observable. lastValueFrom Observable'ni Promise'ga aylantiradi — await bilan kutish uchun. res.set soxtasi qo'yilgan sarlavhalarni obyektga yig'adi.
Tekshirib ko'ring: Nega guard testida
Test.createTestingModuleishlatilmadi, xizmat testida esa ishlatildi?
Javob
Guard'ning bitta oddiy bog'liqligi bor (Reflector) — new bilan yaratish qisqa va aniq. VazifalarService da uchta bog'liqlik, ulardan biri token (@Inject(VAZIFA_OMBORI)) orqali keladi. Test moduli tokenlarni Nest'dagidek bog'laydi va dekoratorlar to'g'ri yozilganini ham tekshiradi. Qoida: bog'liqlik ko'p yoki token orqali bo'lsa — test moduli; bitta-ikkita oddiy klass bo'lsa — new.
8. Natija: biz ishga tushirdik
Formatlangan test/birlik.test.ts ni loyiha nusxasida yurgizdik (NestJS 12.2.0, Vitest 5.0.5, Node 24.21.0; boshlanish vaqti olib tashlandi):
npx vitest run test/birlik.test.ts --reporter=verbose ✓ test/birlik.test.ts > VazifalarService (mock ombor, DI) > yarat: ombor chaqiriladi, 'vazifa.yaratildi' chiqadi 196ms
✓ test/birlik.test.ts > VazifalarService (mock ombor, DI) > ol: ombor undefined → TopilmadiXatosi (404) 4ms
✓ test/birlik.test.ts > VazifalarService (mock ombor, DI) > yangila: faqat false → true da 'vazifa.bajarildi' 6ms
✓ test/birlik.test.ts > overrideProvider: XABARCHI almashtirildi, hodisa → xabarchi 3ms
✓ test/birlik.test.ts > JsonTanaGuard: belgisiz — o'tadi; belgili + text/plain — 415 1ms
✓ test/birlik.test.ts > SxemaPipe: body → 422, param → 400, sxemasiz — o'zgarishsiz 8ms
✓ test/birlik.test.ts > VazifaSarlavhalariInterceptor: ETag va Last-Modified 2ms
Test Files 1 passed (1)
Tests 7 passed (7)Vaqtlarga qarang: birinchi test 196 ms, qolganlari 1–8 ms. Birinchi test Nest va Zod modullarini yuklash narxini to'laydi. Shu faylni birinchi marta (sovuq keshda) yurgizganimizda birinchi test 7 438 ms oldi — mashina band edi. Unit testlar tez, lekin import qimmat: fayl qancha ko'p paket yuklasa, shuncha sekin boshlanadi.
9. Vitest yoki Jest — Nest 12 da
Nest 11 gacha nest new loyihasiga Jest qo'yardi. Nest 12 shabloni ESM va Vitest bilan keladi (Nest CLI va loyiha tuzilmasi). Jest hali ham ishlaydi, eski Nest loyihalarining ko'pi Jest'da (Jest va eski test muhitlari).
Kanon ikkalasini bitta nest new demo loyihasida o'lchadi: bitta unit va bitta e2e test, uch aylana. Jest — 30.5.2 va @swc/jest 0.2.39, ESM rejimida (--experimental-vm-modules). Nima uchun bu muhim: Nest 12 ning o'z paketlari endi faqat ESM. Rasmiy ko'chish qo'llanmasi (migration guide, 2026-11 holatiga) aynan shuni ogohlantiradi — Jest bu paketlarni faqat Node 24.9 va yangisida yuklay oladi; eskiroq Node'da "testlarni Vitest'ga ko'chiring" deydi. Kanon Node 24.21 da ishladi. Bitta mashina, mashina band — mutlaq vaqt emas, nisbat muhim:
- Jest (unit + e2e birga)mediana 4 772 ms100 %
- Vitest (faqat unit)mediana 7 081 ms148 %
- Vitest (unit + e2e, ikki yugurish)mediana 14 906 ms312 %
Manba: Kanon o'lchovi (IlmHamroh, 2026-10): nest new demo, 3 aylana, bitta mashina, bir vaqtda
Kichik loyihada Jest tezroq chiqdi. Sabab — Vitest har ishga tushishda Vite'ni ko'taradi, bu boshlang'ich narx. Shablonda unit va e2e uchun ikkita alohida config bor — demak ikki marta to'lanadi. Katta to'plamda bu narx ko'p testga bo'linadi. Kanonning o'z to'plamida (25 fayl, 131 test) Vitest 7–15 s oldi. Jest esa kanon to'plamida sinalmagan: 25 faylning importlarini qayta yozish kerak edi.
Kanon nega baribir Vitest'da qoldi:
| Mezon | Vitest | Jest |
|---|---|---|
| Nest 12 sukuti | ha | yo'q (alohida sozlash) |
| ESM | tabiiy | --experimental-vm-modules bilan |
| API | vi.fn, vi.mock |
jest.fn, jest.mock |
| Kichik demo tezligi | sekinroq | tezroq |
Bitta muhim tafsilot: Vitest kodni Vite (oxc) bilan o'giradi, build esa tsc bilan. Ular dekoratorlarni har xil o'giradi. Kanonda shunday holat topilgan: aylanma import Vitest'da o'tadi, build'dan keyin esa ilova yiqiladi (Circular dependency). Shuning uchun loyihaning check skriptiga build ham qo'shiladi — buni standalone ilova darsida qilamiz.
10. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
Nest can't resolve dependencies … in the RootTestModule |
test modulida bog'liqlik yo'q | providers ga { provide, useValue } |
kutilmagan chaqiruv: ombor.then (yoki boshqa then xatosi) |
Proxy soxtasi then ga funksiya qaytardi |
then uchun undefined |
.get is not a function |
compile() oldida await yo'q |
await Test.createTestingModule(...).compile() |
overrideProvider ta'sir qilmadi |
compile() dan keyin chaqirildi |
almashtirish — compile() dan oldin |
| Test o'tdi, lekin ilovada xato | soxta haqiqiy xulqni takrorlamaydi | shartnoma testi (Backend test strategiyasi) va e2e |
Oxirgi qator eng xavflisi. Soxta ombor siz yozgan narsani qaytaradi — haqiqiy Postgres esa boshqacha javob berishi mumkin. 26–27-qismlardagi shartnoma testlari shuning uchun bor: hamma omborlar bir xil test to'plamidan o'tadi. Unit test ular o'rnini bosmaydi, faqat qo'shimcha.
11. Mashqlar
Mashqlar kurs/mashqlar/30/49-unit-testlar/ da.
1-mashq (oson): zanjirni to'ldiring
- Test moduli:
await Test.createTestingModule({ providers }).(). - Tayyor nusxani olish:
m.(VazifalarService). - Token o'rniga tayyor qiymat:
{ provide: LOG, : logYarat("silent") }. - Import qilingan moduldagi provider'ni almashtirish:
.(XABARCHI).useValue(f). - Bog'liqlik yo'q bo'lsa, xabardagi modul nomi: .
Yechim
- compile — DI konteynerni yig'adi va Promise qaytaradi.
- get — konteynerdagi tayyor nusxa.
- useValue — klass yaratilmaydi, berilgan qiymat o'zi beriladi.
- overrideProvider —
compile()dan oldin chaqiriladi. - RootTestModule —
createTestingModuleyaratgan yashirin modul.
2-mashq (o'rta): soxta funksiya yozing
vi.fn() ning kichik modelini yozing: soxtaFn() funksiya qaytarsin. U chaqirilganda argumentlarni f.chaqiruvlar massiviga qo'shsin. f.qaytarsin(q) — keyingi hamma chaqiruvlar q ni qaytarsin; f.birMarta(q) — faqat navbatdagi bitta chaqiruv. Navbat bo'sh bo'lsa — qaytarsin qiymati. Ishora: bir martalik qiymatlar uchun massiv va shift() (massiv metodlari).
Yechim
function soxtaFn() {
let doimiy;
const navbat = [];
const f = (...args) => {
f.chaqiruvlar.push(args);
return navbat.length > 0 ? navbat.shift() : doimiy;
};
f.chaqiruvlar = [];
f.qaytarsin = (q) => {
doimiy = q;
return f;
};
f.birMarta = (q) => {
navbat.push(q);
return f;
};
return f;
}
const ol = soxtaFn()
.birMarta({ bajarildi: false })
.birMarta({ bajarildi: true })
.qaytarsin(undefined);
console.log(ol(1));
console.log(ol(1));
console.log(ol(2));
console.log("chaqiruvlar:", ol.chaqiruvlar.length,
JSON.stringify(ol.chaqiruvlar));Konsolda:
{ bajarildi: false }
{ bajarildi: true }
undefined
chaqiruvlar: 3 [[1],[1],[2]]Bu vi.fn().mockReturnValueOnce(a).mockReturnValueOnce(b) ning ishlash tartibi: avval bir martalik navbat, keyin doimiy qiymat. return f — zanjir yozish uchun (.birMarta(...).qaytarsin(...)). Haqiqiy vi.fn yana ko'p narsa eslaydi: this, natija, tashlangan xato.
3-mashq (qiyin): guard'ni soxta kontekst bilan sinash
Paketsiz model. rolGuard(kerakli) funksiyasi { canActivate(k) } obyektini qaytarsin: k.switchToHttp().getRequest().rol kerakli ga teng bo'lsa true, rol umuman yo'q bo'lsa — "401" xabarli Error, boshqa rol bo'lsa — "403" xabarli Error tashlasin. kontekst(req) soxta kontekst yasovchisini yozing va uchta holatni tekshirib, natijani chiqaring.
Yechim
function rolGuard(kerakli) {
return {
canActivate(k) {
const { rol } = k.switchToHttp().getRequest();
if (rol === undefined) throw new Error("401");
if (rol !== kerakli) throw new Error("403");
return true;
},
};
}
function kontekst(req) {
return { switchToHttp: () => ({ getRequest: () => req }) };
}
const guard = rolGuard("admin");
for (const req of [{ rol: "admin" }, {}, { rol: "aziz" }]) {
try {
console.log(JSON.stringify(req), "→",
guard.canActivate(kontekst(req)));
} catch (e) {
console.log(JSON.stringify(req), "→ xato", e.message);
}
}Konsolda:
{"rol":"admin"} → true
{} → xato 401
{"rol":"aziz"} → xato 403Soxta kontekstda faqat guard ishlatadigan metod bor — switchToHttp().getRequest(). Haqiqiy loyihada xuddi shu test Vitest'da expect(() => ...).toThrow("403") bilan yoziladi. Guard 401 va 403 ni farqlashi — Global auth guard darsidagi qoida.
12. Loyiha qadami: test/birlik.test.ts
Bu va keyingi dars bitta qadam: vazifalar-api ga Nest unit testlari qo'shiladi, mavjud HTTP testlari e2e vazifasini bajaradi. Commit — NestJS e2e testlari darsida.
Yangi fayl test/birlik.test.ts. Uning hamma qismlari yuqorida — «Xizmatni soxta ombor bilan sinash», «overrideProvider» va «Guard, pipe va interceptor — alohida» bo'limlarida. Fayl boshidagi importlar va v() yordamchisi:
// birlik.test.ts — Nest unit testlari: Test.createTestingModule,
// useValue mock'lar (vi.fn), overrideProvider, guard/pipe/interceptor
// alohida. 25–27 testlari asosan HTTP integratsiya (testing trophy);
// bu yerda — DI orqali bitta klassni alohida tekshirish naqshlari
import { describe, expect, test, vi } from "vitest";
import { Test } from "@nestjs/testing";
import { EventEmitter2 } from "@nestjs/event-emitter";
import type {
ArgumentMetadata,
CallHandler,
ExecutionContext,
} from "@nestjs/common";
import { of, lastValueFrom } from "rxjs";
import { z } from "zod";
import {
VazifalarService,
HODISA,
} from "../src/vazifalar/vazifalar.service.ts";
import {
WebhookTinglovchi,
} from "../src/vazifalar/vazifalar.tinglovchi.ts";
import { LOG, VAZIFA_OMBORI, XABARCHI } from "../src/tokenlar.ts";
import { logYarat } from "../src/umumiy/log.ts";
import { JsonTanaGuard } from "../src/umumiy/json-tana.guard.ts";
import { SxemaPipe } from "../src/umumiy/sxema.pipe.ts";
import {
VazifaSarlavhalariInterceptor,
} from "../src/umumiy/vazifa-sarlavhalari.interceptor.ts";
import {
ParametrXatosi,
TopilmadiXatosi,
ValidatsiyaXatosi,
} from "../src/umumiy/xatolar.ts";
import { Reflector } from "@nestjs/core";
import type { Vazifa } from "../src/vazifalar/vazifa.ts";
import type { VazifaOmbori } from "../src/vazifalar/ombor.ts";
const v = (o: Partial<Vazifa> = {}): Vazifa => ({
id: 1,
matn: "Non",
bajarildi: false,
teglar: [],
yaratildi: "2026-10-28T00:00:00.000Z",
yangilandi: "2026-10-28T00:00:00.000Z",
tartib: 1,
versiya: 1,
...o,
});Keyin shu tartibda: Soxtalar turi va soxtaOmbor, xizmatModuli, describe bloki (uch test), overrideProvider testi, kontekst, guard, pipe va interceptor testlari.
Kanon faylida qatorlar uzun; biz ularni telefon uchun bo'ldik va test nomlarini biroz qisqartirdik — ma'nosi o'zgarmadi. Formatlangan faylni loyiha nusxasida tekshirdik: npm run tip 0 xato, eslint 0, Vitest 7/7 («Natija» bo'limidagi chiqish).
Tekshiruv:
npm run tip
npm run lint
npx vitest run test/birlik.test.tsButun to'plam (npm test) — keyingi darsda, e2e testlar bilan birga. Hali commit qilmang.
13. Real ishda
- Nest loyihalarida har
*.service.tsyonida*.service.spec.tsbo'ladi —nest g serviceuni o'zi yaratadi. Ko'p jamoada review'dagi birinchi savol: "spec bormi?". - Mock kutubxonalari:
@golevelup/ts-vitest(yoki Jest uchun@golevelup/ts-jest)createMock<T>()beradi — Proxy asosida, xuddi kanondagisoxtaOmborkabi. Biz ularni sinamadik; g'oyasi darsdagi bilan bir xil. - Ortiqcha mock xavfi: hamma narsa soxta bo'lgan test faqat "kod o'zini o'zi chaqiradi" degan narsani tekshiradi. Kanon qoidasi: mock — faqat o'z chegaramizda (ombor, tashqi HTTP), qolgani haqiqiy.
- Intervyu: "Nest'da xizmatni qanday unit test qilasiz?", "
useValuevaoverrideProviderfarqi?", "Guard'ni ilovasiz qanday sinaysiz?".
Xulosa
Test.createTestingModule({...}).compile()— faqat DI konteyner; HTTP, middleware va global sozlamalar yo'q.- Sinalayotgan klass — haqiqiy, bog'liqliklari —
useValuebilan soxta;vi.fn()chaqiruvlarni eslab qoladi. overrideProvider(vaoverrideGuard,overridePipe…) — tayyor ro'yxatnicompile()dan oldin almashtiradi.- Proxy soxtasi
thengaundefinedqaytarsin — aks holda Nest uni Promise deb chaqiradi. - Guard, pipe, interceptor —
new+ soxtaExecutionContext/ArgumentMetadatabilan alohida sinaladi. - Nest 12 sukuti — Vitest; kichik demoda Jest tezroq, lekin kanon ESM va bir xil vosita uchun Vitest'da qoldi.
Keyingi dars: NestJS e2e testlari — butun ilovani ko'tarib HTTP orqali sinaymiz, main.ts sozlamasini testda unutish tuzog'ini ko'ramiz va bu qadamni commit qilamiz.
Manbalar
- NestJS hujjati — Migration guide (Jest va ESM: Node 24.9+, 2026-11-02 da o'qildi): https://docs.nestjs.com/migration-guide
- NestJS hujjati — Testing (
Test.createTestingModule,overrideProvider,overrideGuard): https://docs.nestjs.com/fundamentals/testing - Vitest hujjati — Mock functions (
vi.fn,mockResolvedValueOnce): https://vitest.dev/api/mock - Kanon (IlmHamroh, 2026-10-28):
test/birlik.test.ts, NestJS 12.2.0, Vitest 5.0.5; Vitest va Jest o'lchovi — kanon o'lchovi.thentuzog'i, unutilganLOGxabari va 7/7 natija — biz ishga tushirdik (2026-11-02, Node 24.21.0).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!