Mundarija (27)
- Bu darsda
- 1. Nega bu kerak?
- 2. Nest e2e testi: to'rt qadam
- 2.1 Shablondagi e2e
- 2.2 Unit va e2e yonma-yon
- 3. Eng katta tuzoq: main.ts testga o'tmaydi
- 3.1 Tajriba
- 3.2 Ikki davo
- 3.3 vazifalar-api da: ilovaYarat()
- 4. Supertest bilan vazifalar-api
- 5. overrideGuard — himoyani testda almashtirish
- 6. Haqiqiy baza: Testcontainers
- 6.1 Nima uchun
- 6.2 Kanon natijasi
- 6.3 Nega har test toza bazada
- 7. app.close() va beqaror testlar
- 7.1 Nega yopish shart
- 7.2 Yuk ostida timeout
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): qaysi biri testga o'tadi?
- 2-mashq (o'rta): bitta sozlash funksiyasi
- 3-mashq (qiyin): resurs yopilmasa
- 10. Loyiha qadami: test to'plami va commit
- 11. Real ishda
- Xulosa
- Manbalar
NestJS e2e testlari: createNestApplication, supertest, overrideGuard va Testcontainers
Qisqacha: E2e (end-to-end) testda butun Nest ilovasi ko'tariladi va unga haqiqiy HTTP so'rov yuboriladi. Nest usuli:
Test.createTestingModule({ imports: [AppModule] })→createNestApplication()→init()→request(app.getHttpServer())(supertest). Eng katta tuzoq —main.tsdagi sozlamalar (setGlobalPrefix,useGlobalPipes, middleware) testga o'zi o'tmaydi. Davosi — ilovani yasovchi bitta funksiya:main.tsham, testlar ham uni chaqiradi. Har testdan keyinapp.close(), baza esa har test oldidan toza holatga qaytariladi.
Bu darsda
- Nest e2e testining to'rt qadamini yozasiz va unit testdan farqini aytasiz.
main.tssozlamasi testda yo'qligi qanday xato berishini haqiqiy chiqishda ko'rasiz va oldini olasiz.overrideGuardbilan himoyani testda almashtirasiz.- Testcontainers bilan haqiqiy Postgres'da test qanday ishlashini va har testni qanday tozalashni tushuntirasiz.
vazifalar-apining butun test to'plamini yurgizib, unit testlar qadamini commit qilasiz.
Oldin bilishingiz kerak: NestJS unit testlari, Supertest bilan HTTP integration test, Testcontainers, Test DB strategiyalari, Global enhancer'lar va bajarilish tartibi.
O'tgan darsda bitta klassni soxta bog'liqliklar bilan sinadik. Bunday test tez, lekin u HTTP qatlamini umuman ko'rmaydi. Bugun teskari tomonga o'tamiz: hamma narsa haqiqiy, faqat tashqi chegaralar boshqariladi.
1. Nega bu kerak?
Unit test VazifalarService to'g'ri ishlashini ko'rsatdi. Lekin foydalanuvchi xizmatni chaqirmaydi — u POST /v1/vazifalar yuboradi. So'rov yo'lida ko'p narsa bor: so'rov ID middleware'i, CORS, JSON parser, guard, pipe, interceptor, xato filtri, versiyalash. Ularning birortasi noto'g'ri ulangan bo'lsa, unit testlar yashil qoladi, ilova esa ishlamaydi.
25-qismda bu xavfni HTTP integratsiya testlari bilan yopgandik (Supertest bilan HTTP integration test). Nest'ga ko'chishda ular o'zgarmadi — va aynan ular ko'chish to'g'ri ekanini isbotladi. Nest hujjati bunday testlarni e2e deb ataydi: ilovaning bir uchidan (HTTP so'rov) ikkinchi uchigacha (javob, baza).
O'xshatish: dvigatel stendda yaxshi ishladi (unit test). Endi mashinani to'liq yig'ib, yo'lga chiqarib sinaymiz — rul, tormoz, g'ildirak hammasi birga. Yo'l esa yopiq poligon: haqiqiy shahar ko'chasi emas, lekin haqiqiy asfalt. E2e testdagi baza — shu poligon.
2. Nest e2e testi: to'rt qadam
2.1 Shablondagi e2e
nest new shabloni test/app.e2e-spec.ts ni ham yaratadi:
describe('AppController (e2e)', () => {
let app: INestApplication<App>;
beforeEach(async () => {
const moduleFixture: TestingModule =
await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!');
});
afterEach(async () => {
await app.close();
});
});| Qadam | Nima qiladi |
|---|---|
createTestingModule({ imports: [AppModule] }) |
butun ilova modul daraxti — unit testdagi kabi alohida klasslar emas |
createNestApplication() |
HTTP adapter (Express) va ilova obyektini yasaydi |
await app.init() |
marshrutlar, middleware, lifecycle hook'lar ulanadi; port ochilmaydi |
request(app.getHttpServer()) |
supertest serverni vaqtincha tinglatib, so'rov yuboradi |
app.close() |
onModuleDestroy hook'lari, pool va taymerlar yopiladi |
Shablonda unit va e2e uchun ikkita alohida Vitest config bor: vitest.config.ts (**/*.spec.ts) va vitest.config.e2e.ts (**/*.e2e-spec.ts). npm run test:e2e ikkinchisini ishlatadi. vazifalar-api da esa 25-qismdagi nomlar saqlandi: hamma testlar test/*.test.ts, bitta config.
2.2 Unit va e2e yonma-yon
flowchart TB
subgraph U["Unit test"]
U1["Test.createTestingModule (providers)"] --> U2["VazifalarService"]
U3["soxta ombor"] --> U2
end
subgraph E["E2e test"]
E1["supertest so'rovi"] --> E2["middleware, guard, pipe"]
E2 --> E3["controller → xizmat"]
E3 --> E4["haqiqiy ombor: xotira yoki Postgres"]
E3 --> E5["filtr → JSON javob"]
endChap blokda — bitta klass va uning soxtasi. O'ng blokda — so'rovning butun yo'li. E2e testda soxta narsa kam: odatda faqat tashqi xizmatlar (webhook, email) va vaqt.
Tekshirib ko'ring: Nega
app.init()port ochmaydi, lekin supertest baribir so'rov yubora oladi?
Javob
app.getHttpServer() Node'ning http.Server obyektini qaytaradi — hali tinglamayotgan. Supertest uni o'zi listen(0) bilan vaqtincha tasodifiy portga ulaydi, so'rov yuboradi va yopadi (Supertest bilan HTTP integration test). Shuning uchun testlar parallel ishlaganda port to'qnashmaydi.
3. Eng katta tuzoq: main.ts testga o'tmaydi
3.1 Tajriba
Kichik demo ilova qildik (biz ishga tushirdik: NestJS 12.2.0, Vitest 5.0.5, Node 24.21.0). main.ts da ilovaga global prefiks qo'yiladi — hamma marshrut /api/... bo'ladi:
@Injectable()
export class SoatXizmati {
hozir(): string {
return new Date().toISOString();
}
}
@Controller("salom")
export class SalomController {
constructor(private readonly soat: SoatXizmati) {}
@Get()
ol() {
return { xabar: "Salom, Toshkent!", vaqt: this.soat.hozir() };
}
@Post()
yarat(@Body() tana: { ism?: string }) {
return { salom: tana.ism ?? "mehmon" };
}
}
@Module({ controllers: [SalomController], providers: [SoatXizmati] })
export class AppModule {}
// main.ts va testlar BIR XIL sozlamani chaqiradi
export function sozla(app: INestApplication): INestApplication {
app.setGlobalPrefix("api");
return app;
}Birinchi test shablon uslubida — AppModule import qilinadi, lekin sozla() chaqirilmaydi:
test("main.ts sozlamasi testda yo'q — 404", async () => {
const m = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = m.createNestApplication({ logger: false });
await app.init();
const r = await request(app.getHttpServer()).get("/api/salom");
console.log("prefikssiz:", r.status, r.body.message);
});prefikssiz: 404 Cannot GET /api/salomIlovada GET /api/salom ishlaydi. Testda esa 404. Sabab: createNestApplication() faqat modul daraxtini oladi. main.ts da app. bilan qilingan hamma narsa — setGlobalPrefix, useGlobalPipes, useGlobalFilters, enableVersioning, enableCors, app.use(...) — testda yo'q.
Bu misolda test yiqildi — yaxshi, darrov ko'rdik. Yomoni teskari holat: main.ts da app.useGlobalPipes(new ValidationPipe()) bor, testda yo'q. Test noto'g'ri tanani yuboradi, pipe ishlamaydi, controller uni qabul qiladi — va test o'tib ketadi. Ilovada esa boshqa xulq. Test ilovani emas, boshqa narsani sinagan bo'ladi.
3.2 Ikki davo
1-davo: enhancer'larni modulga ko'chirish. APP_PIPE, APP_GUARD, APP_FILTER, APP_INTERCEPTOR provider'lari modulning ichida turadi (Global enhancer'lar va bajarilish tartibi). Test AppModule ni import qilganda ular ham keladi. vazifalar-api da SxemaPipe, JsonTanaGuard, Problem filtri shu yo'l bilan ulangan.
2-davo: ilovani yasovchi bitta funksiya. Modulga sig'maydigan sozlamalar ham bor: Express middleware'lari (helmet, cors, JSON parser), versiyalash, Swagger. Ular uchun bitta funksiya yoziladi — main.ts ham, testlar ham uni chaqiradi. Demoda bu sozla():
test("sozla() + overrideProvider — 200, vaqt qotirilgan",
async () => {
const m = await Test.createTestingModule({
imports: [AppModule],
})
.overrideProvider(SoatXizmati)
.useValue({ hozir: () => "2026-11-02T09:00:00.000Z" })
.compile();
app = sozla(m.createNestApplication({ logger: false }));
await app.init();
const r = await request(app.getHttpServer())
.get("/api/salom")
.expect(200);
console.log("sozla bilan:", JSON.stringify(r.body));
await request(app.getHttpServer())
.post("/api/salom")
.send({ ism: "Malika" })
.expect(201)
.expect({ salom: "Malika" });
});sozla bilan: {"xabar":"Salom, Toshkent!","vaqt":"2026-11-02T09:00:00.000Z"}Ikki narsaga qarang. Prefiks endi bor — 200. Vaqt esa qotirilgan: overrideProvider e2e testda ham ishlaydi. Soatni almashtirish — e2e'dagi kam sonli joiz soxtalardan biri (Vaqt va tasodifni boshqarish). POST uchun Nest sukut statusi 201 — .expect(201) shuni tekshiradi.
3.3 vazifalar-api da: ilovaYarat()
Kanon 2-davoni 25-qismdan beri ishlatadi. src/ilova.ts dagi ilovaYarat(b) ilovani to'liq yasaydi va listen() qilmaydi. main.ts uni chaqirib portni ochadi, testlar esa portsiz ishlatadi. Ichidagi tartib (qisqartirilgan parcha):
export async function ilovaYarat(b: Bogliqliklar) {
const app = await NestFactory.create<NestExpressApplication>(
AppModule.forRoot({ /* ombor, config, log … */ }),
{ bodyParser: false, bufferLogs: true, abortOnError: false },
);
app.use(sorovId());
// … log, CLS, helmet, cors, statik fayllar …
app.use(express.json({ limit: "16kb", type: JSON_TURLARI }));
app.use(tanaXatosi);
app.enableVersioning({ type: VersioningType.URI /* … */ });
hujjatniUla(app); // Swagger: /docs
await app.init();
return app;
}Shuning uchun kanon createNestApplication() ni umuman ishlatmaydi: ilovaYarat() ning o'zi NestFactory.create ni chaqiradi. Testlar uchun kichik yordamchi bor — test/yordamchi.ts:
/** supertest uchun: Nest ilovasi (init qilingan, listen
* qilinmagan) http serveri */
export async function httpIlova(b: Bogliqliklar): Promise<Server> {
return (await ilovaYarat(b)).getHttpServer();
}Bogliqliklar ichida testlar ombor (new XotiraOmbori()), CORS ro'yxati, log va webhook xabarchisini o'zi beradi. Ya'ni "main.ts dagi ilova + test bergan chegaralar". «Global pipe'larni testda takrorlash» tuzog'i bu loyihada yo'q — takrorlanadigan narsaning o'zi yo'q.
Tekshirib ko'ring:
main.tsdaapp.useGlobalFilters(new ProblemFilter())yozilgan, e2e test shablon uslubida (createNestApplication()). Xato javoblari testda qanday ko'rinadi?
Javob
Filtr testda yo'q, shuning uchun xatolarni Nest'ning sukut filtri qayta ishlaydi: {"statusCode":404,"message":"…"} shaklida, application/problem+json emas. Problem Details'ni tekshiradigan test yiqiladi — yoki undan yomoni, bunday test umuman yozilmagan bo'lsa, farqni hech kim sezmaydi. Davo: filtrni APP_FILTER bilan modulga ko'chirish yoki ilovani bitta funksiyada yasash.
4. Supertest bilan vazifalar-api
25-qismdagi supertest testlari Nest'da o'zgarmasdan qoldi — faqat httpIlova() endi async. Birinchi testni o'qiymiz (qatorlar telefon uchun bo'lingan):
const yangiIlova = () => httpIlova({
ombor: new XotiraOmbori(),
corsOriginlar: ["http://localhost:5173"],
});
test("supertest: yaratish → Location → o'qish", async () => {
const app = await yangiIlova();
const yarat = await request(app)
.post("/v1/vazifalar")
.set("X-Request-Id", "supertest-0001")
.send({ matn: "Non olish #uy" })
.expect(201)
.expect("Content-Type", /application\/json/)
.expect("X-Request-Id", "supertest-0001");
const joy = String(yarat.headers.location);
assert.equal(joy, "/v1/vazifalar/1");
const ol = await request(app).get(joy).expect(200)
.expect("ETag", '"1"');
assert.deepEqual((ol.body as { teglar: string[] }).teglar, ["uy"]);
});Bitta testda butun zanjir: so'rov ID middleware'i sarlavhani qaytardi, pipe tanani tekshirdi, xizmat teglarni ajratdi, interceptor ETag qo'ydi. Har test o'z ilovasini va o'z xotira omborini yaratadi — testlar orasida umumiy holat yo'q.
Fayldagi beshta testni yurgizdik (biz ishga tushirdik, vazifalar-api nusxasi):
✓ test/supertest.test.ts > supertest: yaratish → Location → o'qish (zanjir: .send().expect()) 329ms
✓ test/supertest.test.ts > supertest: shartli so'rov — 304 (supertest fetch emas: no-cache qo'shmaydi) 64ms
✓ test/supertest.test.ts > supertest: Problem Details va CORS preflight 70ms
✓ test/supertest.test.ts > supertest agent: bir xil ilova, ketma-ket so'rovlar (cookie bo'lsa saqlanardi — 28-qism) 62ms
✓ test/supertest.test.ts > CORS: config'dagi oxiri / li origin — Access-Control-Allow-Origin baribir chiqadi 46ms
Test Files 1 passed (1)
Tests 5 passed (5)Ikkinchi test nomidagi izohga e'tibor bering: shartli so'rov (If-None-Match) fetch bilan emas, supertest bilan yuboriladi. fetch bunday so'rovga o'zi Cache-Control: no-cache qo'shadi va server 304 bermaydi — 25-qismdagi topilma.
5. overrideGuard — himoyani testda almashtirish
Ilovada himoyalangan marshrut bor deylik. Har e2e testda haqiqiy kirish jarayonini o'tish og'ir. Ba'zi testlar uchun guard'ni o'chirib qo'yish mumkin. Demoda x-kalit sarlavhasini talab qiladigan guard:
@Injectable()
export class KalitGuard implements CanActivate {
canActivate(k: ExecutionContext): boolean {
const req = k.switchToHttp().getRequest<{
headers: Record<string, string | undefined>;
}>();
return req.headers["x-kalit"] === "maxfiy";
}
}
@Controller("hisobot")
@UseGuards(KalitGuard)
export class HisobotController {
@Get()
ol() {
return { jami: 3 };
}
}Test ikki xil ilova yasaydi — haqiqiy guard bilan va almashtirilgan bilan:
async function ilova(ochiq: boolean) {
let b = Test.createTestingModule({ imports: [HisobotModule] });
if (ochiq) {
b = b.overrideGuard(KalitGuard)
.useValue({ canActivate: () => true });
}
app = (await b.compile()).createNestApplication({ logger: false });
return app.init();
}Natija (biz ishga tushirdik):
haqiqiy: 403 {"message":"Forbidden resource","error":"Forbidden","statusCode":403}
almashtirilgan: 200 {"jami":3}Guard false qaytarsa, Nest 403 beradi va o'z sukut xabarini yozadi: "Forbidden resource" — "taqiqlangan resurs". overrideGuard uni { canActivate: () => true } bilan almashtirdi — marshrut ochildi.
Diqqat: Guard'ni o'chirgan test himoyani sinamaydi. Har himoyalangan marshrut uchun kamida bitta test haqiqiy guard bilan bo'lsin: "kirmagan foydalanuvchi — 401", "boshqa rol — 403". Aks holda guard ulanmay qolsa ham hamma test yashil bo'ladi.
vazifalar-api da auth guard hali yo'q — u 28-qismdagi auth Nest'ga ko'chganda keladi (Global auth guard, @Public va @Roles). U yerda kanon overrideGuard o'rniga boshqa yo'lni tanlaydi: testlar haqiqiy sessiya bilan kiradi. Sabab yuqoridagi ogohlantirishda.
6. Haqiqiy baza: Testcontainers
6.1 Nima uchun
Xotira ombori tez, lekin u Postgres emas. Trigger, UNIQUE cheklovi, RLS, tranzaksiya — ular faqat haqiqiy bazada tekshiriladi. 27-qismda bunga Testcontainers ni o'rgangandik (Testcontainers): test o'zi Docker'da Postgres konteynerini ko'taradi, migratsiyalarni qo'llaydi va oxirida o'chiradi.
Nest'ga ko'chishda test/konteyner.test.ts o'zgarmadi — u omborni to'g'ridan sinaydi, Nest'ga tegmaydi. Kanon uni Nest ilovasi bilan bitta to'plamda yurgizdi. Boshlanishi:
const YOQIQ = process.env["TESTCONTAINERS"] === "1";
if (!YOQIQ) {
test.skip("Testcontainers — TESTCONTAINERS=1 bilan " +
"ishga tushiriladi (Docker kerak)", () => {});
} else {
const { PostgreSqlContainer } =
await import("@testcontainers/postgresql");
// …
beforeAll(async () => {
const k = await new PostgreSqlContainer("postgres:18")
.withTmpFs({ "/var/lib/postgresql": "rw" })
.start();
// prisma migrate deploy — konteyner bazasiga
await k.snapshot(); // migratsiyalangan bo'sh baza → andoza
});
// har testdan oldin: await konteyner.restoreSnapshot();
}Bu — qisqartirilgan parcha: konteyner nomi, vaqt o'lchash va pool qatorlari olib tashlangan. Asosiy g'oya uch qatorda: konteyner ko'tarish, snapshot() (andoza) va har test oldidan restoreSnapshot().
Test ixtiyoriy (opt-in): oddiy npm test da u SKIP. Docker kerak, va har kungi tez tekshiruvni sekinlashtirmasligi kerak. TESTCONTAINERS=1 npm test bilan yoqiladi.
6.2 Kanon natijasi
Biz Docker'ni yoqmadik — chiqish kanon tekshiruvidan — loyihaning unit testlar qo'shilgan bosqichi. Qatorda uchta narsani toping: har bosqich vaqti, Tests 10 passed va oxirgi qism — testdan keyin qolgan konteynerlar soni:
--- Testcontainers (TESTCONTAINERS=1): [Testcontainers vaqtlari] konteyner 2366 ms, migratsiya 1862 ms, snapshot 518 ms, restore (oxirgi) 236 ms Tests 10 passed (10) ; kanon30-tc qoldiqlari: 0- konteyner ko'tarishbir marta2 366 ms
- migratsiyabir marta1 862 ms
- snapshotbir marta518 ms
- restorehar test oldidan236 ms
Manba: Kanon o'lchovi (IlmHamroh, 2026-10): unit testlar qo'shilgan bosqich tekshiruvi, postgres:18, bitta mashina
Grafikka qarang: qimmat qismlar — bir marta, fayl boshida. Har test faqat restore ni to'laydi. 27-qismda tozalash usullari o'lchangandi: TRUNCATE + ma'lumot ~100 ms, tranzaksiya + ROLLBACK ~19 ms, andozadan yangi baza ~350 ms (Test DB strategiyalari). Restore andoza usulining o'zi.
Oxirgi qismdagi "qoldiqlari: 0" — testdan keyin bitta ham konteyner qolmadi. afterAll da konteyner.stop() shuni ta'minlaydi.
6.3 Nega har test toza bazada
Bir test qo'shgan qator keyingi testga ta'sir qilsa — testlar tartibga bog'liq bo'ladi. Yakka o'tadi, birga yiqiladi, yoki teskarisi. Buni kichik modelda ko'ramiz (brauzerda ishlaydi):
// Testcontainers g'oyasi modeli: migratsiyadan keyingi holat —
// andoza, har testdan oldin undan toza nusxa
function bazaYarat() {
let jadval = [];
let andoza = null;
return {
qosh: (matn) => jadval.push({ id: jadval.length + 1, matn }),
soni: () => jadval.length,
snapshot: () => { andoza = structuredClone(jadval); },
restore: () => { jadval = structuredClone(andoza); },
};
}
const baza = bazaYarat();
baza.qosh("namuna: Non olish"); // "migratsiya + seed"
baza.snapshot();
const testlar = {
"ikki vazifa qo'shiladi": () => {
baza.qosh("Sut");
baza.qosh("Choy");
return baza.soni() === 3;
},
"bo'sh boshlanadi (faqat namuna)": () => baza.soni() === 1,
};
for (const tozalash of [false, true]) {
console.log(tozalash ? "restore bilan:" : "tozalashsiz:");
for (const [nom, fn] of Object.entries(testlar)) {
if (tozalash) baza.restore();
console.log(" ", fn() ? "✓" : "✗", nom);
}
}Konsolda:
tozalashsiz:
✓ ikki vazifa qo'shiladi
✗ bo'sh boshlanadi (faqat namuna)
restore bilan:
✓ ikki vazifa qo'shiladi
✓ bo'sh boshlanadi (faqat namuna)Tozalashsiz ikkinchi test yiqildi — u birinchisining qatorlarini ko'rdi. restore bilan ikkalasi ham toza boshlanadi. structuredClone — chuqur nusxa (Sayoz va chuqur nusxalash). Model soddalashtirilgan: haqiqiy restoreSnapshot() Postgres'da bazani andozadan qayta yaratadi (CREATE DATABASE … TEMPLATE …).
7. app.close() va beqaror testlar
7.1 Nega yopish shart
app.init() ilova resurslarini ochadi: Postgres pool'i, Redis ulanishi, @Interval taymerlari (Rejalashtirish). Test tugagach ular ochiq qolsa — jarayon tugamaydi, yoki keyingi test ulanish chegarasiga uriladi. app.close() onModuleDestroy va onApplicationShutdown hook'larini chaqiradi (Lifecycle hook'lar) — vazifalar-api da SaqlashYopuvchi pool'ni shu yerda yopadi.
Demo testlarda afterEach(async () => { await app?.close(); }) — har testdan keyin. ?. — test ilovani yaratishga ulgurmay yiqilgan bo'lsa ham xato bermasligi uchun.
7.2 Yuk ostida timeout
Kanon tekshiruvida halol topilma bor. Birinchi yugurishda mashina boshqa og'ir ishlar bilan band edi: tip 63 s, lint 71 s oldi (odatda 5–10 s). Ikki test timeout bilan yiqildi: jarayon (60 s — bola jarayon ishga tushiradi) va prisma-ombor (20 s). Ikkinchi yugurishda hammasi o'tdi.
Bu kod xatosi emas, lekin muammo. Bola jarayon va baza bilan ishlaydigan testlar yuk ostida sekinlashadi. Kanon vitest.config.ts da sukut 5 s ni 20 s ga oshirgan (testTimeout: 20_000), hook'lar uchun 30 s. Beqaror (flaky) testni "qayta yurgiz, o'tadi" deb qoldirish xavfli: haqiqiy xato ham shunday ko'rinadi (Test strategiyasi va flaky testlar).
8. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| Testda 404, ilovada ishlaydi | setGlobalPrefix/versiyalash faqat main.ts da |
ilovani bitta funksiyada yasang |
| Testda validatsiya ishlamaydi | useGlobalPipes faqat main.ts da |
APP_PIPE yoki umumiy funksiya |
| Xato javobi shakli boshqacha | filtr useGlobalFilters bilan |
APP_FILTER |
| Vitest tugamaydi yoki "pool to'ldi" | app.close() yo'q |
afterEach/afterAll da yoping |
| Testlar yakka o'tadi, birga yiqiladi | umumiy baza holati | har test oldidan tozalash |
| Guard o'chirilgan, himoya sinalmagan | faqat overrideGuard li testlar |
haqiqiy guard bilan 401/403 testlari |
9. Mashqlar
Mashqlar kurs/mashqlar/30/50-e2e-testlar/ da.
1-mashq (oson): qaysi biri testga o'tadi?
main.ts da yozilgan va modulda yozilgan sozlamalar. Shablon uslubidagi e2e testda (createNestApplication()) ular bormi?
app.setGlobalPrefix("api")—{ provide: APP_PIPE, useClass: SxemaPipe }(AppModuleda) —app.useGlobalFilters(new ProblemFilter())—@UseGuards(KalitGuard)(controller'da) —
Yechim
- yo'q —
app.bilan qilingan hamma narsa ilova obyektida, modulda emas. - bor —
APP_PIPE— modul provider'i,AppModulebilan keladi. - yo'q —
useGlobalFiltershamapp.usuli. - bor — dekorator controller klassida, modul bilan birga keladi.
2-mashq (o'rta): bitta sozlash funksiyasi
Paketsiz model. ilovaYasa() marshrutlar jadvalini qaytarsin: { "/salom": () => "Salom" }. sozla(ilova, prefiks) hamma kalitlarga prefiks qo'shgan yangi obyekt qaytarsin. sorov(ilova, yol) — topilsa 200 <javob>, topilmasa 404. Ikki "test" yozing: biri sozla siz, biri bilan; ikkalasi ham /api/salom so'rasin. Ishora: Object.entries va Object.fromEntries (Obyekt metodlari).
Yechim
function ilovaYasa() {
return { "/salom": () => "Salom" };
}
function sozla(ilova, prefiks) {
return Object.fromEntries(
Object.entries(ilova).map(([yol, f]) => [prefiks + yol, f]),
);
}
function sorov(ilova, yol) {
const f = ilova[yol];
return f ? `200 ${f()}` : "404";
}
// "main.ts": sozla(ilovaYasa(), "/api")
const testlar = {
"sozlasiz test": ilovaYasa(),
"sozla bilan test": sozla(ilovaYasa(), "/api"),
};
for (const [nom, ilova] of Object.entries(testlar)) {
console.log(`${nom}: ${sorov(ilova, "/api/salom")}`);
}Konsolda:
sozlasiz test: 404
sozla bilan test: 200 SalomModel darsdagi haqiqiy tajribani takrorlaydi: modul (ilovaYasa) bir xil, farq faqat sozlamada. main.ts ham sozla ni chaqirsa, test va ilova bir xil bo'ladi.
3-mashq (qiyin): resurs yopilmasa
Paketsiz model. ilovaOch() "ochiq ulanishlar" hisoblagichini (ochiq) bittaga oshirsin va { yop() } qaytarsin — yop() hisoblagichni kamaytirsin, ikkinchi marta chaqirilsa hech narsa qilmasin. Uchta "test" yurgizing: birinchi va uchinchisi yop() ni finally da chaqirsin, ikkinchisi chaqirmasin va xato tashlasin. Oxirida ochiq ni chiqaring. Keyin ikkinchi testni ham finally bilan tuzating. Ishora: try/finally (try, catch, finally).
Yechim
let ochiq = 0;
function ilovaOch() {
ochiq++;
let yopildi = false;
return {
yop() {
if (yopildi) return;
yopildi = true;
ochiq--;
},
};
}
function yurgiz(nom, fn, yopish) {
const app = ilovaOch();
try {
fn();
console.log("✓", nom);
} catch (e) {
console.log("✗", nom, "—", e.message);
} finally {
if (yopish) app.yop();
}
}
const xatoli = () => {
throw new Error("kutilmagan 500");
};
yurgiz("birinchi", () => {}, true);
yurgiz("ikkinchi (yopilmaydi)", xatoli, false);
yurgiz("uchinchi", () => {}, true);
console.log("ochiq ulanishlar:", ochiq);
ochiq = 0;
yurgiz("ikkinchi (finally bilan)", xatoli, true);
console.log("tuzatilgandan keyin:", ochiq);Konsolda:
✓ birinchi
✗ ikkinchi (yopilmaydi) — kutilmagan 500
✓ uchinchi
ochiq ulanishlar: 1
✗ ikkinchi (finally bilan) — kutilmagan 500
tuzatilgandan keyin: 0Yiqilgan test ham resursni yopishi kerak — shuning uchun yopish finally da (Vitest'da — afterEach). Bitta yopilmagan ilova haqiqiy loyihada bitta ochiq pool degani: Vitest jarayoni tugamay osilib turadi.
10. Loyiha qadami: test to'plami va commit
O'tgan darsda test/birlik.test.ts qo'shildi. E2e uchun yangi fayl kerak emas: 25–27-qismlardagi HTTP testlari (api, rest, supertest, toplam, idempotentlik …) aynan e2e vazifasini bajaradi va ilovaYarat() orqali haqiqiy Nest ilovasini ko'taradi. Testcontainers — ixtiyoriy.
Butun to'plamni yurgizing:
npm run tip
npm run lint
npm test
npm run build
TESTCONTAINERS=1 npm test # Docker bo'lsaKanon natijasi (bazali testlar bilan, Docker'dagi Postgres 18.6):
--- npm run tip
exit 0 (6181 ms)
--- npm run lint
exit 0 (13926 ms)
--- npm run test
↓ test/konteyner.test.ts (1 test | 1 skipped)
Test Files 25 passed | 1 skipped (26)
Tests 130 passed | 1 skipped (131)
exit 0 (14776 ms)
--- npm run build
exit 0 (6690 ms)130 ta test o'tdi, bitta SKIP — Testcontainers (opt-in). Uning alohida natijasi «Kanon natijasi» bo'limidagi: 10/10. Lokal bazangiz yo'q bo'lsa, bazali testlar (pg-ombor, prisma-ombor, migratsiya) o'zi SKIP bo'ladi — npm test yiqilmaydi.
Hammasi yashil bo'lsa — commit:
git add test/birlik.test.ts
M="test: Nest unit testlari — TestingModule + vi.fn mock'lar,"
M+=" overrideProvider, guard/pipe/interceptor alohida"
git commit -m "$M"11. Real ishda
- Nest loyihalarida e2e testlar odatda
test/*.e2e-spec.tsda, alohida config bilan va CI'da alohida bosqich sifatida yuradi. Sababi — ular sekinroq va baza talab qiladi. - "Ilova fabrikasi" (bizdagi
ilovaYarat) — katta jamoalarda keng tarqalgan naqsh:createApp()yokibootstrapApp()nomi bilan. Kod review'da "bu sozlamamain.tsda qoldi, testda yo'q" — tez-tez uchraydigan izoh. - Testcontainers CI'da Docker talab qiladi; GitHub Actions'ning Linux runner'larida Docker bor (33–34-qismlarda CI va Docker'ni o'rganamiz).
- Intervyu: "Unit va e2e test farqi?", "E2e testda nega validatsiya ishlamay qolishi mumkin?", "Testlar orasida bazani qanday tozalaysiz?".
Xulosa
- E2e:
createTestingModule({ imports: [AppModule] })→createNestApplication()→init()→ supertest; port ochilmaydi. main.tsdagiapp.sozlamalari testga o'tmaydi; davosi —APP_*provider'lar va ilovani yasovchi bitta funksiya.vazifalar-apidailovaYarat()—main.tsva testlar uchun bitta; 25–27 HTTP testlari Nest'da o'zgarmay e2e bo'ldi.overrideProvider/overrideGuarde2e'da ham ishlaydi, lekin himoya haqiqiy guard bilan ham sinalsin.- Testcontainers: qimmat qism bir marta, har test oldidan
restoreSnapshot(); har testdan keyinapp.close().
Keyingi dars: Fastify adapter bilan NestJS — Express o'rniga Fastify: bir xil endpoint besh xil serverda o'lchanadi va kanon nega Express'da qolgani ko'rsatiladi.
Manbalar
- NestJS hujjati — Testing, End-to-end testing: https://docs.nestjs.com/fundamentals/testing#end-to-end-testing
- Testcontainers for Node.js — PostgreSQL moduli, snapshot: https://node.testcontainers.org/modules/postgresql/
- Kanon (IlmHamroh, 2026-10-28):
ilovaYarat(),test/yordamchi.ts,test/konteyner.test.ts, unit testlar bosqichi tekshiruvi (Vitest 130/131, Testcontainers 10/10). Demo testlar (prefiks tuzog'i,overrideProvider,overrideGuard) vasupertest.test.tsnatijasi — biz ishga tushirdik (2026-11-02, NestJS 12.2.0, Vitest 5.0.5, Node 24.21.0).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!