8.11-bob: Testing — Jest, unit test va e2e test (chuqur)
8-QISM — NestJS (chuqur) · 11-mavzu
1. Kirish va motivatsiya
Auth/email (8.9, 8.10) ni bildik. Endi NestJS'ning professional, ish sifatini belgilaydigan qismiga — testlash ga — o'tamiz. Test — havaskor va professional dasturchini eng aniq ajratadigan ko'nikma: kod ishlaydi deb taxmin qilish o'rniga, isbotlash. Katta loyihada (sizning karyerangizda) test'siz kod — qum ustiga qurilgan bino (har o'zgarish — qo'rquv). Test bilan — har o'zgarishda ishonch (regressiya ushlanadi). NestJS — DI 8.2-bob tufayli test uchun yaratilgan framework (mock oson — 8.2: 2.17).
NestJS testlash uchun Jest (test framework) va Supertest (HTTP test) ni tug'ma beradi. Ikki xil test: unit test (bitta birlik — service/controller — izolyatsiyada, bog'liqliklar mock); e2e test (end-to-end — to'liq oqim — real HTTP so'rov DB). NestJS'ning @nestjs/testing (Test.createTestingModule) — DI'ni test'da ham beradi: bog'liqliklarni mock bilan almashtirish (8.2: 2.17). Bu — DI'ning eng katta foydasi.
Bu bob — promptdagidan chuqurroq: nega test, Jest asoslari (describe/it/expect), unit test (service mock — 8.2), e2e test (Supertest — to'liq oqim), test izolyatsiyasi, mock turlari, va coverage. Bu bob 8.2 (DI) ni amalda ko'rsatadi (mock). Bu bob: Jest, unit/e2e, mock, izolyatsiya — chuqur. Test — sifatli, ishonchli, qo'rquvsiz kodning kafolati.
O'xshatish: test — mashina ishlab chiqarishdagi sifat nazorati. Har detal (unit test — service) alohida tekshiriladi (to'g'ri ishlaydimi). Keyin yig'ilgan mashina (e2e test — to'liq ilova) yo'lda sinaladi (haqiqiy sharoit — HTTP so'rov). Mock — soxta detal (test'da real dvigatel emas, simulyator). Sifat nazoratisiz mashina (test'siz kod) — yo'lda buzilishi mumkin (production'da xato). Test — kafolat.
Nega muhim?
- Sifat kafolati — kod ishlaydi (taxmin emas, isbot).
- Qo'rquvsiz o'zgartirish — regressiya ushlanadi (refactoring xavfsiz).
- DI foydasi — mock oson (8.2: 2.17) — NestJS test uchun.
- Professional — test — senior dasturchi belgisi (ish talabi).
2. Nazariya — chuqur tushuntirish
2.1. Nega test (qiymati)
Test'siz:
"Ishlaydi shekilli" (qo'lda tekshirish — to'liq emas)
Har o'zgarish — qo'rquv (nima buzildi?)
Regressiya (eski xato qaytadi)
Test bilan:
Kod ishlashi ISBOTLANGAN (avtomatik)
O'zgartirish ishonchli (test o'tsa — ishlaydi)
Hujjat (test — kod qanday ishlashini ko'rsatadi)
Tez fikr-mulohaza (xato darrov)Test qiymati: kod ishlaydi deb isbotlash (taxmin emas). Eng katta foyda — qo'rquvsiz o'zgartirish (test o'tsa, ishonch). Regressiya (eski xato) ushlanadi. Katta/jamoaviy loyihada — majburiy. Test — investitsiya (vaqt sarflaysiz, lekin keyin tejaysiz).
2.2. Test turlari (piramida)
Test piramidasi:
/\
/e2e\ — kam (sekin, qimmat) — to'liq oqim (HTTPDB)
/─────\
/ integ. \ — o'rtacha — bir necha modul birga
/───────────\
/ unit \ — ko'p (tez, arzon) — bitta birlik (service — mock)
Ko'p unit (tez), kam e2e (kritik oqim)Test piramidasi: unit (ko'p — bitta birlik, mock, tez); integration (o'rtacha — bir necha birga); e2e (kam — to'liq oqim, sekin). Ko'p unit (asos), kam e2e (kritik flow — login, buyurtma). Bu — samarali test strategiyasi (tez + ishonchli).
2.3. Jest asoslari (describe, it, expect)
Jest — test framework:
describe("Kalkulyator", () => { // test guruhi
it("ikki sonni qo'shadi", () => { // bitta test
const natija = summa(2, 3);
expect(natija).toBe(5); // tasdiq (assertion)
});
it("manfiy son", () => {
expect(summa(-1, 1)).toBe(0);
});
});Jest:
describe(guruh),it/test(test),expect(...).matcher(tasdiq). Matcher'lar:toBe(===),toEqual(chuqur),toContain,toThrow,toHaveBeenCalled(mock).beforeEach/afterEach(har test oldidan/keyin — tayyorlash). Bu — test asosi.
2.4. Jest matcher'lar
expect(x).toBe(5); // === (primitiv)
expect(obj).toEqual({ a: 1 }); // chuqur tenglik (obyekt)
expect(arr).toContain(3); // massivda bor
expect(x).toBeNull(); toBeUndefined(); toBeTruthy(); toBeFalsy();
expect(() => f()).toThrow(); // xato tashlaydi
await expect(promise).resolves.toBe(5); // async (resolve)
await expect(promise).rejects.toThrow(); // async (reject)
expect(mock).toHaveBeenCalledWith(arg); // mock chaqirildiMatcher'lar — tasdiq turlari:
toBe(primitiv ===),toEqual(obyekt chuqur),toThrow(xato),resolves/rejects(async — 2.11-JS),toHaveBeenCalled*(mock — 2.6). To'g'ri matcher — aniq test.
2.5. Unit test — TestingModule (DI — 8.2)
Unit test — bitta birlik (service), bog'liqliklar mock (NestJS DI):
import { Test, TestingModule } from "@nestjs/testing";
describe("UsersService", () => {
let service: UsersService;
const mockRepo = { find: jest.fn(), findOne: jest.fn(), save: jest.fn() }; // mock (8.2: 2.17)
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({ // test moduli (DI)
providers: [
UsersService,
{ provide: getRepositoryToken(User), useValue: mockRepo }, // real o'rniga mock (8.2: 2.9)
],
}).compile();
service = module.get<UsersService>(UsersService);
});
it("hammasini qaytaradi", async () => {
mockRepo.find.mockResolvedValue([{ id: 1 }]); // mock natija
const natija = await service.hammasi();
expect(natija).toEqual([{ id: 1 }]);
expect(mockRepo.find).toHaveBeenCalled();
});
});Unit test + TestingModule:
Test.createTestingModule(test DI moduli) real bog'liqlik o'rniga mock (useValue— 8.2: 2.17).getRepositoryToken(User)— repository mock 8.3-bob. Service izolyatsiyada (DB'siz). DI'ning eng katta foydasi (8.2: 2.17) — bu yerda.
2.6. Mock va spy (jest.fn)
const mockFn = jest.fn(); // bo'sh mock
mockFn.mockReturnValue(5); // qaytaradi
mockFn.mockResolvedValue(user); // async qaytaradi
mockFn.mockRejectedValue(new Error()); // xato
// Tekshirish
expect(mockFn).toHaveBeenCalled(); // chaqirildimi
expect(mockFn).toHaveBeenCalledWith(1, "a"); // qaysi argument bilan
expect(mockFn).toHaveBeenCalledTimes(2); // necha marta
// Spy — real funksiyani kuzatish (yoki o'zgartirish)
jest.spyOn(service, "metod").mockResolvedValue(...);Mock (
jest.fn()) — soxta funksiya (natija/xato beriladi, chaqiruv tekshiriladi).mockResolvedValue(async),mockRejectedValue(xato). Spy (jest.spyOn) — real funksiyani kuzatish/o'zgartirish.toHaveBeenCalledWith— qaysi argument bilan (8.2: 2.17). Bu — test izolyatsiyasining asosi.
2.7. Controller unit test
describe("UsersController", () => {
let controller: UsersController;
const mockService = { hammasi: jest.fn(), yarat: jest.fn() };
beforeEach(async () => {
const module = await Test.createTestingModule({
controllers: [UsersController],
providers: [{ provide: UsersService, useValue: mockService }], // service mock
}).compile();
controller = module.get<UsersController>(UsersController);
});
it("yangi user yaratadi", async () => {
const dto = { ism: "Ali", email: "a@b.uz" };
mockService.yarat.mockResolvedValue({ id: 1, ...dto });
expect(await controller.yarat(dto as any)).toEqual({ id: 1, ...dto });
expect(mockService.yarat).toHaveBeenCalledWith(dto);
});
});Controller unit test — controller'ni service mock bilan (service'ni alohida test qilgansiz — 2.5). Controller faqat HTTP (8.1: 2.7) — service'ga uzatishni tekshiradi. DI mock (service). Bu — qatlam test (controller alohida).
2.8. e2e test — Supertest (to'liq oqim)
e2e test — to'liq ilova, real HTTP so'rov (Supertest):
import { Test } from "@nestjs/testing";
import * as request from "supertest";
describe("Users (e2e)", () => {
let app: INestApplication;
beforeAll(async () => {
const module = await Test.createTestingModule({
imports: [AppModule], // BUTUN ilova (mock yo'q — real)
}).compile();
app = module.createNestApplication();
app.useGlobalPipes(new ValidationPipe()); // production bilan bir xil (8.5)
await app.init();
});
it("/users (POST) yangi user", () => {
return request(app.getHttpServer()) // real HTTP (Supertest)
.post("/users")
.send({ ism: "Ali", email: "ali@a.uz", parol: "Parol123" })
.expect(201)
.expect((res) => expect(res.body.email).toBe("ali@a.uz"));
});
afterAll(async () => { await app.close(); });
});e2e test + Supertest: butun ilova (
AppModule— mock yo'q, real DB/oqim),request(app.getHttpServer())— real HTTP so'rov (.post().send().expect()). To'liq oqim (controller service DB javob). Kritik flow (login, buyurtma) uchun.app.init()/close()(8.1: 2.10).
2.9. Test DB (e2e uchun)
e2e test DB:
- Alohida test DB (production'ga tegmaslik)
- Har test oldidan tozalash (izolyatsiya — 2.10)
- Yoki: in-memory DB (sqlite), Docker test DB 10.3-bob
- Yoki: tranzaksiya + rollback (har test'dan keyin)
Production DB'da TEST QILMANG (ma'lumot buziladi)e2e test DB — alohida (test DB, in-memory sqlite, yoki Docker — 10.3). Har test oldidan tozalash (izolyatsiya). Production DB'da test qilmang (ma'lumot buziladi). Bu — e2e'ning murakkab qismi (real DB kerak).
2.10. Test izolyatsiyasi (mustaqillik)
Har test MUSTAQIL bo'lishi kerak:
beforeEach — har test oldidan toza holat (mock reset, DB toza)
Testlar bir-biriga ta'sir qilmasin (tartib muhim emas)
jest.clearAllMocks() — mock'larni tozalash
Test boshqa testga bog'liq (biri o'zgarsa, boshqasi buziladi)Test izolyatsiyasi: har test mustaqil (boshqasiga bog'liq emas).
beforeEach— toza holat (mock reset, DB toza).jest.clearAllMocks(). Tartibga bog'liq test — yomon (biri o'zgarsa, kaskad). Izolyatsiya — ishonchli test.
2.11. Guard/pipe/interceptor test (8.6)
// Guard test
describe("RolesGuard", () => {
it("admin'ga ruxsat beradi", () => {
const guard = new RolesGuard(mockReflector);
const context = mockExecutionContext({ user: { rol: "admin" } });
mockReflector.getAllAndOverride.mockReturnValue(["admin"]);
expect(guard.canActivate(context)).toBe(true);
});
});
// ExecutionContext mock helper
function mockExecutionContext(reqData): any {
return {
switchToHttp: () => ({ getRequest: () => reqData }),
getHandler: () => ({}), getClass: () => ({}),
};
}Guard/pipe/interceptor test 8.6-bob — ExecutionContext mock bilan. Guard
canActivate— true/false tekshiriladi. Mock context (switchToHttp/getHandler). Bu — RBAC 8.7-bob, auth 8.9-bob guard'larini test qilish.
2.11a. Mock berish usullari — useValue / useFactory / useClass (8.2)
TestingModule'da bog'liqlikni almashtirishning uch usuli bor (8.2: 2.9 — provider turlari test'da ham amal qiladi):
// 1. useValue — tayyor mock obyekt (eng ko'p ishlatiladigan)
{ provide: getRepositoryToken(User), useValue: mockRepo }
// 2. useFactory — mock'ni funksiya yaratadi (dinamik, konfiguratsiyaga qarab)
{
provide: ConfigService,
useFactory: () => ({
get: jest.fn((kalit: string) => {
const qiymatlar: Record<string, string> = {
JWT_SECRET: "test-secret",
JWT_EXPIRES: "1h",
};
return qiymatlar[kalit]; // har kalitga mos qiymat
}),
}),
}
// 3. useClass — soxta klass (real interfeysga mos alternativ implementatsiya)
class FakeMailService {
async tasdiqlashYubor() { return true; } // real email yubormaydi
}
{ provide: MailService, useClass: FakeMailService }Mock berish usullari:
useValue(tayyor mock obyekt — eng oddiy va ko'p ishlatiladigan);useFactory(mock'ni funksiya yaratadi — dinamik, ConfigService kabi kalitga qarab qiymat qaytaradiganlar uchun qulay);useClass(soxta klass — real interfeysga mos, murakkab xatti-harakat kerak bo'lganda). Uchalasi 8.2 2.9-bob dagi provider turlarining aynan o'zi — DI test'da ham bir xil ishlaydi.
2.11b. overrideProvider / overrideGuard / overridePipe (e2e)
e2e test'da butun AppModule import qilinadi, lekin ba'zi bog'liqliklarni (tashqi email, to'lov API, yoki qat'iy guard) almashtirish kerak bo'ladi. Buning uchun .compile() dan oldin override* metodlari ishlatiladi:
const module = await Test.createTestingModule({
imports: [AppModule], // butun ilova (real)
})
.overrideProvider(MailService) // real email o'rniga
.useValue({ tasdiqlashYubor: jest.fn().mockResolvedValue(true) })
.overrideProvider(PaymentService) // real to'lov API o'rniga
.useValue({ tolov: jest.fn().mockResolvedValue({ status: "ok" }) })
.overrideGuard(ThrottlerGuard) // rate-limit'ni o'chirish (8.6)
.useValue({ canActivate: () => true }) // hamma so'rovga ruxsat
.overrideGuard(JwtAuthGuard) // auth guard'ni soxta user bilan
.useValue({
canActivate: (context: ExecutionContext) => {
const req = context.switchToHttp().getRequest();
req.user = { id: 1, rol: "admin" }; // soxta autentifikatsiya
return true;
},
})
.compile();override* metodlari (e2e): butun
AppModulereal, lekin ayrim qismlarni almashtirish uchun..overrideProvider(X).useValue(mock)— provider'ni mock bilan (tashqi email/to'lov API);.overrideGuard(G).useValue({...})— guard'ni almashtirish (rate-limit'ni o'chirish yoki soxta auth — token yasashsiz himoyalangan endpoint'ni test qilish);.overridePipe(P)ham bor..compile()dan oldin chaqiriladi. Bu — e2e'da tashqi dunyoni izolyatsiya qilishning asosiy vositasi.
2.11c. Fixture'lar va faker (test ma'lumoti)
Testlarda takrorlanadigan ma'lumot (user, buyurtma) yaratish uchun fixture (tayyor namuna) va @faker-js/faker (soxta realistik ma'lumot generatori) ishlatiladi:
import { faker } from "@faker-js/faker";
// Fixture factory — kerakli maydonlarni override qilib namuna beradi
function userYasa(override: Partial<User> = {}): User {
return {
id: faker.number.int({ min: 1, max: 1000 }),
ism: faker.person.fullName(),
email: faker.internet.email(),
parol: faker.internet.password({ length: 12 }),
yaratilgan: faker.date.past(),
...override, // testga xos maydonni ustiga yozish
} as User;
}
it("admin roli tekshiriladi", () => {
const admin = userYasa({ rol: "admin" }); // faqat rol muhim, qolgani realistik
// ... AAA 2.14-bob: Arrange tayyor
});
// Static fixture (aniq, o'zgarmas kutilgan qiymat kerak bo'lsa)
export const testUser: User = {
id: 1, ism: "Test User", email: "test@example.uz", rol: "user",
} as User;Fixture + faker:
userYasa(override)— fixture factory (namuna generatori): faqat testga muhim maydonnioverridebilan beriladi, qolgani realistik to'ldiriladi.@faker-js/faker— soxta lekin realistik ma'lumot (ism, email, sana). Bu takroriy{ id: 1, ism: "..." }yozishni kamaytiradi va AAA'ning Arrange qismini toza qiladi. Ogohlantirish: natijani aniq solishtirish kerak bo'lsa (toEqual),faker'ning tasodifiy qiymati o'rniga static fixture yokifaker.seed(123)(takrorlanuvchi) ishlating — aks holda test beqaror bo'ladi (flaky).
2.11d. Test DB — in-memory va Testcontainers (10.3)
e2e'da real DB kerak. Uch keng tarqalgan yondashuv:
// 1. In-memory SQLite (tez, ornatishsiz — lekin Postgres'dan farqlari bor)
TypeOrmModule.forRoot({
type: "sqlite",
database: ":memory:", // xotirada, diskda emas
entities: [User, Order],
synchronize: true, // sxema avtomatik (faqat test!)
})
// 2. Testcontainers — real Postgres'ni Docker'da vaqtincha ko'taradi (10.3)
import { PostgreSqlContainer } from "@testcontainers/postgresql";
let container: StartedPostgreSqlContainer;
beforeAll(async () => {
container = await new PostgreSqlContainer("postgres:16").start(); // konteyner ko'tariladi
process.env.DB_URL = container.getConnectionUri(); // ilovaga uzatiladi
}, 60_000); // Docker sekin — timeout oshiriladi
afterAll(async () => { await container.stop(); }); // test tugagach o'chiriladi
// 3. Tranzaksiya + rollback (har test'dan keyin ma'lumotni orqaga qaytarish)
beforeEach(async () => { queryRunner = dataSource.createQueryRunner();
await queryRunner.startTransaction(); });
afterEach(async () => { await queryRunner.rollbackTransaction(); }); // toza holatTest DB usullari: (1) In-memory SQLite (
:memory:) — tez, o'rnatishsiz; kamchilik: Postgres'ning ba'zi imkoniyatlari (JSONB, ba'zi tiplar) farq qiladi — production DB'dan farqli xatti-harakat mumkin. (2) Testcontainers 10.3-bob — real Postgres'ni Docker konteynerida vaqtincha ko'taradi (.start()/.stop()) — production bilan aynan bir xil, lekin Docker kerak va sekinroq (timeout oshiriladi). (3) Tranzaksiya + rollback — har test tranzaksiyada ishlaydi, keyin bekor qilinadi (izolyatsiya — 2.10, tez). Kritik e2e uchun Testcontainers eng ishonchli.
2.12. Mocking strategiyalari (over-mock'dan qochish)
Tashqi bog'liqlik mock (DB, email, API — tez, izolyatsiya)
Oddiy utility — real (mock keraksiz — over-mock)
jest-mock-extended (avtomatik mock — interface)
Faqat kerakli metodlarni mock (qolgani kerak emas)
Over-mocking — hamma narsani mock (test real kodni tekshirmaydi)Over-mocking'dan qoching (best practices): tashqi bog'liqlik (DB, email — 2.5) mock; oddiy utility — real (mock keraksiz). Hamma narsani mock — test real mantiqni tekshirmaydi (faqat mock'ni).
jest-mock-extended— interface'dan avtomatik mock. Balans: izolyatsiya, lekin real kod.
2.13. Coverage (qamrov)
npm run test:cov # qamrov hisoboti Coverage — kod qancha test bilan qoplangani (%):
- Statements, Branches, Functions, Lines
- 80%+ — yaxshi maqsad (lekin 100% emas — sifat > miqdor)
Coverage — sifat kafolati EMAS (test mantiqli bo'lishi kerak)Coverage — kod qancha test bilan qoplangani (
test:cov). 80%+ — yaxshi (lekin 100% maqsad emas — sifat muhim). Yuqori coverage ≠ yaxshi test (yomon test ham coverage beradi). Coverage — yo'l-yo'riq, kafolat emas. Kritik kod (auth, to'lov) — to'liq test.
2.14. Test best practices (AAA naqsh)
AAA naqsh (har test):
Arrange — tayyorlash (mock, data)
Act — bajarish (funksiyani chaqirish)
Assert — tasdiqlash (expect)
Bir test — bir narsa (aniq nom — "yangi user yaratganda email yuboradi")
Izolyatsiya (mustaqil — 2.10); AAA (toza)
Edge case'lar (null, bo'sh, xato — corner case)
CI'da avtomatik (har commit — 10)AAA naqsh (Arrange-Act-Assert) — toza test tuzilmasi: tayyorla, bajar, tekshir. Bir test — bir narsa (aniq nom). Edge case'lar (xato, null — 2.4: resolves/rejects). CI'da (10) avtomatik (har commit test). Bu — sifatli test.
2.15. Test va CI/CD (10 ko'prik)
CI/CD'da test (10):
- Har commit/PR test ishga tushadi (GitHub Actions)
- Test o'tmasa merge bloklanadi (sifat darvozasi)
- Coverage hisoboti
- e2e — alohida (sekinroq)
package.json: "test", "test:e2e", "test:cov" (8.1: Misol 12)Test — CI/CD (10) da avtomatik (har commit). Test o'tmasa — merge bloklanadi (regressiya production'ga ketmaydi). Bu — jamoa sifat kafolati (15: code review bilan). Test — DevOps (10) bilan birga.
2.16. Best practices (test)
Test piramidasi (ko'p unit, kam e2e — 2.2)
DI mock (TestingModule — 8.2: 2.17, 2.5)
Test izolyatsiyasi (mustaqil — beforeEach — 2.10)
AAA naqsh (toza — 2.14)
Over-mocking'dan qoching (real utility — 2.12)
Edge case'lar (xato, null — 2.4)
e2e — alohida test DB (production emas — 2.9)
CI'da avtomatik (har commit — 2.15, 10)
Kritik kod (auth/to'lov) to'liq test (2.13)3. Sintaksis — tez ma'lumotnoma
// Jest (2.3, 2.4)
describe("X", () => {
beforeEach(() => {});
it("...", () => { expect(natija).toBe(kutilgan); });
});
// Unit (DI mock — 2.5)
const module = await Test.createTestingModule({
providers: [Service, { provide: Dep, useValue: mockDep }],
}).compile();
// Mock 2.6-bob: jest.fn().mockResolvedValue(x); toHaveBeenCalledWith(arg)
// e2e 2.8-bob: request(app.getHttpServer()).post("/x").send(data).expect(201)npm run test npm run test:e2e npm run test:cov npm test -- --watch4. Batafsil kod namunalari
Misol 1 — Service unit test (mock — 2.5)
// users.service.spec.ts
import { Test, TestingModule } from "@nestjs/testing";
import { getRepositoryToken } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { NotFoundException } from "@nestjs/common";
describe("UsersService", () => {
let service: UsersService;
let repo: Repository<User>;
const mockRepo = {
find: jest.fn(),
findOne: jest.fn(),
create: jest.fn(),
save: jest.fn(),
delete: jest.fn(),
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UsersService,
{ provide: getRepositoryToken(User), useValue: mockRepo }, // mock (8.2: 2.17)
],
}).compile();
service = module.get<UsersService>(UsersService);
repo = module.get(getRepositoryToken(User));
});
afterEach(() => jest.clearAllMocks()); // izolyatsiya (2.10)
describe("bitta", () => {
it("user topilsa, qaytaradi", async () => {
const user = { id: 1, ism: "Ali" };
mockRepo.findOne.mockResolvedValue(user); // Arrange
const natija = await service.bitta(1); // Act
expect(natija).toEqual(user); // Assert (AAA — 2.14)
expect(mockRepo.findOne).toHaveBeenCalledWith({ where: { id: 1 } });
});
it("user topilmasa, NotFoundException", async () => {
mockRepo.findOne.mockResolvedValue(null);
await expect(service.bitta(999)).rejects.toThrow(NotFoundException); // xato (2.4)
});
});
describe("yarat", () => {
it("user yaratadi", async () => {
const dto = { ism: "Vali", email: "v@a.uz" };
mockRepo.create.mockReturnValue(dto);
mockRepo.save.mockResolvedValue({ id: 2, ...dto });
const natija = await service.yarat(dto as any);
expect(natija).toEqual({ id: 2, ...dto });
expect(mockRepo.save).toHaveBeenCalled();
});
});
});Misol 2 — Service mock bog'liqliklar (auth — 8.9)
// auth.service.spec.ts
describe("AuthService", () => {
let service: AuthService;
const mockUsersService = { emailBilan: jest.fn(), yarat: jest.fn() };
const mockJwtService = { signAsync: jest.fn() };
const mockMailService = { tasdiqlashYubor: jest.fn() };
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [
AuthService,
{ provide: UsersService, useValue: mockUsersService }, // (8.9)
{ provide: JwtService, useValue: mockJwtService },
{ provide: MailService, useValue: mockMailService }, // (8.10)
{ provide: ConfigService, useValue: { get: jest.fn() } },
],
}).compile();
service = module.get(AuthService);
});
it("login: parol to'g'ri bo'lsa token beradi", async () => {
const user = { id: 1, email: "a@b.uz", parol: await bcrypt.hash("parol", 10) };
mockUsersService.emailBilan.mockResolvedValue(user);
mockJwtService.signAsync.mockResolvedValue("token");
const natija = await service.validateUser("a@b.uz", "parol");
expect(natija).toBeTruthy();
expect(natija.parol).toBeUndefined(); // parolsiz (14, 8.9)
});
it("login: parol xato bo'lsa null", async () => {
const user = { id: 1, parol: await bcrypt.hash("boshqa", 10) };
mockUsersService.emailBilan.mockResolvedValue(user);
expect(await service.validateUser("a@b.uz", "parol")).toBeNull();
});
});Misol 3 — Controller unit test (2.7)
// users.controller.spec.ts
describe("UsersController", () => {
let controller: UsersController;
const mockService = {
hammasi: jest.fn(),
bitta: jest.fn(),
yarat: jest.fn(),
};
beforeEach(async () => {
const module = await Test.createTestingModule({
controllers: [UsersController],
providers: [{ provide: UsersService, useValue: mockService }],
}).compile();
controller = module.get(UsersController);
});
it("hammasi service'ni chaqiradi", async () => {
mockService.hammasi.mockResolvedValue([{ id: 1 }]);
expect(await controller.hammasi({} as any)).toEqual([{ id: 1 }]);
expect(mockService.hammasi).toHaveBeenCalled();
});
it("yarat dto'ni service'ga uzatadi", async () => {
const dto = { ism: "Ali", email: "a@b.uz" };
mockService.yarat.mockResolvedValue({ id: 1, ...dto });
await controller.yarat(dto as any);
expect(mockService.yarat).toHaveBeenCalledWith(dto); // uzatildi (8.1: 2.7)
});
});Misol 4 — e2e test (to'liq oqim — 2.8)
// test/users.e2e-spec.ts
import { Test } from "@nestjs/testing";
import { INestApplication, ValidationPipe } from "@nestjs/common";
import * as request from "supertest";
import { AppModule } from "../src/app.module";
describe("Users (e2e)", () => {
let app: INestApplication;
beforeAll(async () => {
const module = await Test.createTestingModule({
imports: [AppModule], // butun ilova (real — 2.8)
}).compile();
app = module.createNestApplication();
app.useGlobalPipes(new ValidationPipe({ whitelist: true })); // production bilan bir xil (8.5)
await app.init();
});
afterAll(async () => { await app.close(); });
describe("/users (POST)", () => {
it("to'g'ri ma'lumot 201", () => {
return request(app.getHttpServer())
.post("/users")
.send({ ism: "Ali", email: "ali@test.uz", parol: "Parol123!" })
.expect(201)
.expect((res) => {
expect(res.body.email).toBe("ali@test.uz");
expect(res.body.parol).toBeUndefined(); // parolsiz (14)
});
});
it("noto'g'ri email 400 (validatsiya — 8.5)", () => {
return request(app.getHttpServer())
.post("/users")
.send({ ism: "A", email: "xato", parol: "123" })
.expect(400); // ValidationPipe (8.5)
});
});
describe("/users/:id (GET)", () => {
it("yo'q user 404", () => {
return request(app.getHttpServer()).get("/users/99999").expect(404);
});
});
});Misol 5 — e2e auth oqimi (login + himoyalangan — 8.9)
describe("Auth (e2e)", () => {
let app: INestApplication;
let accessToken: string;
beforeAll(async () => { /* app setup — 2.8 */ });
it("signup login himoyalangan", async () => {
// 1. Signup
await request(app.getHttpServer())
.post("/auth/signup")
.send({ ism: "Test", email: "test@a.uz", parol: "Parol123!" })
.expect(201);
// 2. Login token
const login = await request(app.getHttpServer())
.post("/auth/login")
.send({ email: "test@a.uz", parol: "Parol123!" })
.expect(201);
accessToken = login.body.accessToken;
expect(accessToken).toBeDefined();
// 3. Himoyalangan (token bilan)
await request(app.getHttpServer())
.get("/auth/me")
.set("Authorization", `Bearer ${accessToken}`) // token (8.9)
.expect(200)
.expect((res) => expect(res.body.email).toBe("test@a.uz"));
// 4. Tokensiz 401
await request(app.getHttpServer()).get("/auth/me").expect(401);
});
});Misol 6 — Guard test (RBAC — 8.7)
// roles.guard.spec.ts
describe("RolesGuard", () => {
let guard: RolesGuard;
const mockReflector = { getAllAndOverride: jest.fn() };
beforeEach(() => {
guard = new RolesGuard(mockReflector as any);
});
function mockContext(user: any): any {
return {
switchToHttp: () => ({ getRequest: () => ({ user }) }),
getHandler: () => ({}), getClass: () => ({}),
};
}
it("rol talab qilinmasa, ruxsat", () => {
mockReflector.getAllAndOverride.mockReturnValue(undefined);
expect(guard.canActivate(mockContext({ rol: "user" }))).toBe(true);
});
it("admin'ga ruxsat", () => {
mockReflector.getAllAndOverride.mockReturnValue(["admin"]);
expect(guard.canActivate(mockContext({ rol: "admin" }))).toBe(true);
});
it("user'ga admin amali rad (403)", () => {
mockReflector.getAllAndOverride.mockReturnValue(["admin"]);
expect(() => guard.canActivate(mockContext({ rol: "user" }))).toThrow(ForbiddenException);
});
});Misol 7 — Mock bilan xato test (edge case — 2.4)
describe("OrdersService edge cases", () => {
it("zaxira yetmasa, BadRequest", async () => {
mockProductRepo.findOne.mockResolvedValue({ zaxira: 0 });
await expect(service.buyurtmaYarat(1, [{ productId: 1, miqdor: 5 }]))
.rejects.toThrow(BadRequestException); // xato (2.4)
});
it("DB xatosi tarqaladi", async () => {
mockRepo.save.mockRejectedValue(new Error("DB down"));
await expect(service.yarat({} as any)).rejects.toThrow("DB down");
});
it("bo'sh massiv", async () => {
mockRepo.find.mockResolvedValue([]);
expect(await service.hammasi()).toEqual([]); // bo'sh holat
});
});Misol 8 — Test setup va config (2.9, 2.10)
// Umumiy test setup (helper)
export function mockRepository<T = any>(): Partial<Record<keyof Repository<T>, jest.Mock>> {
return {
find: jest.fn(), findOne: jest.fn(), findOneBy: jest.fn(),
create: jest.fn(), save: jest.fn(), update: jest.fn(), delete: jest.fn(),
};
}
// jest.config (package.json yoki jest.config.js)
// {
// "moduleFileExtensions": ["js", "json", "ts"],
// "rootDir": "src",
// "testRegex": ".*\\.spec\\.ts$",
// "transform": { "^.+\\.ts$": "ts-jest" },
// "coverageDirectory": "../coverage",
// }
// package.json scripts (8.1: Misol 12)
// "test": "jest", "test:watch": "jest --watch",
// "test:cov": "jest --coverage", "test:e2e": "jest --config ./test/jest-e2e.json"Misol 9 — Interceptor/Pipe test (8.5, 8.6)
// Pipe test (validatsiya — 8.5)
describe("ParseObjectIdPipe", () => {
const pipe = new ParseObjectIdPipe();
it("to'g'ri ObjectId o'tadi", () => {
const id = "507f1f77bcf86cd799439011";
expect(pipe.transform(id)).toBe(id);
});
it("noto'g'ri ID BadRequest", () => {
expect(() => pipe.transform("xato")).toThrow(BadRequestException);
});
});
// Interceptor test (8.6)
describe("TransformInterceptor", () => {
it("javobni o'raydi", (done) => {
const interceptor = new TransformInterceptor();
const next = { handle: () => of({ id: 1 }) }; // RxJS of (8.6)
interceptor.intercept({} as any, next as any).subscribe((natija) => {
expect(natija).toEqual({ success: true, data: { id: 1 } }); // (8.6: 2.6)
done();
});
});
});Misol 10 — e2e: overrideProvider + overrideGuard (2.11b)
Tashqi email/to'lov'ni mock qilib, auth guard'ni soxta user bilan almashtirib himoyalangan endpoint'ni tokensiz test qilamiz:
// test/orders.e2e-spec.ts
import { Test } from "@nestjs/testing";
import { INestApplication, ExecutionContext, ValidationPipe } from "@nestjs/common";
import * as request from "supertest";
import { AppModule } from "../src/app.module";
import { MailService } from "../src/mail/mail.service";
import { JwtAuthGuard } from "../src/auth/jwt-auth.guard";
describe("Orders (e2e)", () => {
let app: INestApplication;
const mockMail = { tasdiqlashYubor: jest.fn().mockResolvedValue(true) }; // real email yubormaydi
beforeAll(async () => {
const module = await Test.createTestingModule({
imports: [AppModule], // butun ilova (2.8)
})
.overrideProvider(MailService) // real email o'rniga mock (2.11b)
.useValue(mockMail)
.overrideGuard(JwtAuthGuard) // auth guard'ni soxta user bilan almashtirish
.useValue({
canActivate: (ctx: ExecutionContext) => {
ctx.switchToHttp().getRequest().user = { id: 1, rol: "admin" };
return true; // token yasashsiz "kirgan" hisoblanadi
},
})
.compile();
app = module.createNestApplication();
app.useGlobalPipes(new ValidationPipe({ whitelist: true })); // production bilan bir xil (8.5)
await app.init();
});
afterAll(async () => { await app.close(); });
afterEach(() => jest.clearAllMocks()); // izolyatsiya (2.10)
it("himoyalangan endpoint — soxta user bilan 201", () => {
return request(app.getHttpServer())
.post("/orders")
.send({ productId: 1, miqdor: 2 })
.expect(201); // guard override tufayli tokensiz ham o'tadi
});
it("buyurtma yaratilganda email yuboriladi", async () => {
await request(app.getHttpServer())
.post("/orders")
.send({ productId: 1, miqdor: 1 })
.expect(201);
expect(mockMail.tasdiqlashYubor).toHaveBeenCalled(); // tashqi servis mock tekshiruvi
});
});Misol 11 — Fixture factory + faker (2.11c)
// test/fixtures/user.fixture.ts
import { faker } from "@faker-js/faker";
import { User } from "../../src/users/user.entity";
export function userYasa(override: Partial<User> = {}): User {
return {
id: faker.number.int({ min: 1, max: 9999 }),
ism: faker.person.fullName(),
email: faker.internet.email(),
parol: faker.internet.password({ length: 12 }),
rol: "user",
...override, // testga xos maydon (masalan rol) ustiga yoziladi
} as User;
}
// users.service.spec.ts — fixture'dan foydalanish
describe("UsersService (fixture)", () => {
it("admin ro'yxatda ko'rinadi", async () => {
const users = [userYasa(), userYasa({ rol: "admin" })]; // Arrange — realistik ma'lumot
mockRepo.find.mockResolvedValue(users);
const natija = await service.hammasi(); // Act
expect(natija).toHaveLength(2); // Assert (AAA — 2.14)
expect(natija.some((u) => u.rol === "admin")).toBe(true);
});
});Misol 12 — In-memory SQLite bilan integration test (2.11d)
Real repository, real DB (xotirada) — mock'siz, lekin tez:
// users.integration.spec.ts
import { Test } from "@nestjs/testing";
import { TypeOrmModule } from "@nestjs/typeorm";
describe("UsersService (integration — real DB)", () => {
let service: UsersService;
beforeAll(async () => {
const module = await Test.createTestingModule({
imports: [
TypeOrmModule.forRoot({
type: "sqlite",
database: ":memory:", // xotirada (2.11d)
entities: [User],
synchronize: true, // sxema avtomatik (faqat test!)
}),
TypeOrmModule.forFeature([User]),
],
providers: [UsersService], // mock YO'Q — real repository
}).compile();
service = module.get(UsersService);
});
it("yaratilgan user o'qilishi mumkin (real yozib-o'qish)", async () => {
const yaratilgan = await service.yarat({ ism: "Ali", email: "a@b.uz", parol: "x" } as any);
const topilgan = await service.bitta(yaratilgan.id);
expect(topilgan.email).toBe("a@b.uz"); // haqiqiy DB oqimi tekshirildi
});
});Misol 13 — CI'da test (GitHub Actions — 2.15, 10)
Har commit/PR'da test avtomatik ishga tushadi; o'tmasa merge bloklanadi (sifat darvozasi):
# .github/workflows/test.yml
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres: # e2e uchun real test DB (2.9)
image: postgres:16
env: { POSTGRES_PASSWORD: test, POSTGRES_DB: testdb }
ports: ["5432:5432"]
options: >-
--health-cmd pg_isready --health-interval 10s
--health-timeout 5s --health-retries 5 # DB tayyor bo'lguncha kutish
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: "20", cache: "npm" }
- run: npm ci # aniq o'rnatish (10)
- run: npm run test:cov # unit + coverage (2.13)
- run: npm run test:e2e # e2e (2.8)
env: { DB_URL: "postgres://postgres:test@localhost:5432/testdb" }CI'da test (2.15, 10):
on: [push, pull_request]— har commit/PR'da ishga tushadi.services.postgres— e2e uchun real test DB konteyneri (health-cmd— DB tayyor bo'lguncha kutadi).npm ci(10) — aniq, takrorlanuvchi o'rnatish. Test o'tmasa job qizil bo'ladi va merge bloklanadi (sifat darvozasi — regressiya production'ga ketmaydi).
Misol 14 — Test piramidasi (strategiya — 2.2)
Loyiha test strategiyasi 2.2-bob:
Unit (ko'p — har service/controller/guard):
- users.service.spec.ts (Misol 1)
- auth.service.spec.ts (Misol 2)
- users.controller.spec.ts (Misol 3)
- roles.guard.spec.ts (Misol 6)
e2e (kam — kritik oqim):
- auth.e2e-spec.ts (login flow — Misol 5)
- orders.e2e-spec.ts (buyurtma — to'lov flow)
Unit: tez fikr-mulohaza (har metod); e2e: kritik flow ishonchi
CI'da (10): har commit unit; PR'da e2e5. To'g'ri va noto'g'ri holatlar
1) Test'siz kod
"ishlaydi shekilli" (taxmin — 2.1)
unit + e2e (isbot)2) Real DB unit test'da
// real repository (sekin, izolyatsiya yo'q — 2.5)
providers: [UsersService, UserRepository]
// mock
{ provide: getRepositoryToken(User), useValue: mockRepo }3) Over-mocking
hamma narsani mock (real kod tekshirilmaydi — 2.12)
tashqi bog'liqlik mock; oddiy utility real4) Test'lar bir-biriga bog'liq
test tartibga bog'liq (biri buzilsa, kaskad — 2.10)
izolyatsiya (beforeEach, clearAllMocks)5) Production DB'da e2e
production DB (ma'lumot buziladi — 2.9)
test DB (alohida, tozalanadigan)6. Keng tarqalgan xatolar va yechimlari
Xato 1 — Nest can't resolve dependencies (test)
Sababi: TestingModule'da bog'liqlik mock qilinmagan 2.5-bob. Yechimi: har bog'liqlikni useValue mock.
Xato 2 — Mock chaqirilmadi (toHaveBeenCalled false)
Sababi: mock noto'g'ri sozlangan, yoki kod boshqa yo'l. Yechimi: mockResolvedValue; kod oqimini tekshir.
Xato 3 — Async test o'tadi (lekin xato)
Sababi: await yo'q yoki return yo'q 2.8-bob. Yechimi: async/await; return request(...); resolves/rejects.
Xato 4 — e2e test bir-biriga ta'sir
Sababi: DB tozalanmagan 2.10-bob. Yechimi: beforeEach DB toza; tranzaksiya rollback.
Xato 5 — Mock reset bo'lmaydi
Sababi: clearAllMocks yo'q 2.10-bob. Yechimi: afterEach(() => jest.clearAllMocks()).
Xato 6 — e2e sekin/timeout
Sababi: real DB/tashqi servis 2.9-bob. Yechimi: test DB; tashqi servis mock (e2e'da ham); jest timeout oshir.
7. Integratsiya — bu mavzu stack'ning qayerida uchraydi
- DI 8.2-bob: mock (TestingModule) — DI'ning foydasi.
- Service/Controller 8.1-bob: unit test.
- Guards/Pipes (8.5, 8.6): test.
- Auth 8.9-bob: auth service/e2e test.
- DB 8.3-bob: repository mock; test DB.
- CI/CD (10): avtomatik test.
- Clean code (15): test — sifat.
- Frontend 11.16-bob: Vitest/RTL (o'xshash).
8. Eng yaxshi amaliyotlar (best practices)
- Test piramidasi (ko'p unit, kam e2e — 2.2).
- DI mock (TestingModule — useValue — 8.2: 2.17, 2.5).
- Test izolyatsiyasi (mustaqil — beforeEach/clearAllMocks — 2.10).
- AAA naqsh (Arrange-Act-Assert — toza — 2.14).
- Over-mocking'dan qoching (real utility — 2.12).
- Edge case'lar (xato, null, bo'sh — 2.4, Misol 7).
- e2e — alohida test DB (production emas — 2.9).
- CI'da avtomatik (har commit — 2.15, 10).
- Kritik kod to'liq test (auth/to'lov — 2.13).
- Aniq test nomi (nima tekshiriladi — 2.14).
9. Amaliy loyiha: "To'liq Test Qamrovi (NestJS)"
NestJS testlashni mustahkamlash.
Maqsad
NestJS ilovaga to'liq test qamrovi qo'shish: unit (service/controller/guard) va e2e (kritik oqim).
Talablar (requirements)
- Service unit test: mock repository; CRUD + edge case (Misol 1, 7, 2.5).
- Bog'liqlikli service: auth service (mock UsersService/JwtService/MailService — Misol 2).
- Controller unit test: service mock; uzatish tekshiruvi (Misol 3, 2.7).
- Guard test: RolesGuard (ExecutionContext mock — Misol 6, 2.11).
- Pipe/Interceptor test: custom pipe/interceptor (Misol 9).
- e2e test: CRUD oqimi (Supertest — Misol 4, 2.8).
- e2e auth: signup login himoyalangan (Misol 5).
- Izolyatsiya: beforeEach, clearAllMocks 2.10-bob.
- Edge case'lar: xato, null, bo'sh (Misol 7, 2.4).
- Fixture + faker: takroriy ma'lumotni fixture factory bilan (Misol 11, 2.11c).
- e2e override: overrideProvider (email mock) + overrideGuard (soxta auth — Misol 10, 2.11b).
- Integration test: in-memory SQLite bilan real repository (Misol 12, 2.11d).
- Coverage + CI: test:cov; GitHub Actions'da avtomatik (Misol 13, 2.13, 2.15).
Maslahatlar (hint)
- DI mock: getRepositoryToken + useValue (2.5, 1-xato).
- Izolyatsiya: clearAllMocks (2.10, 5-xato).
- e2e: request(app.getHttpServer()) (2.8, 3-xato).
- Async: await/return/rejects (2.4, 3-xato).
- Over-mock'dan qoching (2.12, 3-holat).
- e2e test DB (production emas — 2.9, 5-holat).
"Tayyor" mezonlari (acceptance criteria)
- Service unit test (mock, CRUD).
- Bog'liqlikli service test.
- Controller unit test.
- Guard test.
- Pipe/Interceptor test.
- e2e CRUD.
- e2e auth oqimi.
- Izolyatsiya (beforeEach/clearAllMocks).
- Edge case'lar.
- Fixture + faker (test ma'lumoti).
- e2e override (overrideProvider/overrideGuard).
- Integration test (in-memory DB).
- Coverage (kritik to'liq) + CI (GitHub Actions).
Yechim kodi ataylab berilmagan — bu loyihani o'zingiz yozib ko'ring.
10. Xulosa va keyingi bobga ko'prik
Bu bobda NestJS testlashni chuqur o'rgandik:
- Nega test (isbot, qo'rquvsiz o'zgartirish — 2.1); piramida (ko'p unit, kam e2e — 2.2); Jest (describe/it/expect — 2.3, 2.4).
- Unit test (TestingModule — DI mock — 2.5); mock/spy (jest.fn — 2.6); controller test 2.7-bob; guard/pipe test 2.11-bob; mock berish usullari (useValue/useFactory/useClass — 2.11a).
- e2e test (Supertest — to'liq oqim — 2.8); override* (overrideProvider/overrideGuard — 2.11b); fixture + faker (2.11c); test DB (in-memory/Testcontainers — 2.9, 2.11d); izolyatsiya (mustaqil — 2.10).
- Mocking (over-mock'dan qoching — 2.12); coverage 2.13-bob; AAA 2.14-bob; CI/CD (GitHub Actions — 2.15, Misol 13).
Keyingi bob — 8.12-bob: NestJS Telegram bot (Telegraf). Test'ni bildik; endi amaliy mavzuni — NestJS'da Telegram bot (nestjs-telegraf — 5.14) — o'rganamiz: ro'yxatdan o'tkazish, admin, ma'lumot ko'rsatish, SMS yuborish. 5.14'dagi botni NestJS arxitekturasida (modul, DI, service) quramiz.
Foydalanilgan rasmiy/ishonchli manbalar
- docs.nestjs.com/fundamentals/testing (Test.createTestingModule, overrideProvider/Guard, e2e)
- jestjs.io — Jest (mock, spyOn, expect, coverage, matchers)
- github.com/ladjs/supertest — Supertest (e2e HTTP so'rov testi)
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!