WisarWisar
Dasturlash kitobi/8-QISM — NestJS27 daqiqa

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)

text
  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)

text
  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:

ts
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

ts
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 chaqirildi

Matcher'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):

ts
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)

ts
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

ts
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):

ts
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)

text
  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)

text
  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)

ts
// 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):

ts
// 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:

ts
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 AppModule real, 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:

ts
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 maydonni override bilan 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 yoki faker.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:

ts
// 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 holat

Test 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)

text
   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)

bash
npm run test:cov                     # qamrov hisoboti
text
  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)

text
  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)

text
  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)

text
   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

ts
// 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)
bash
npm run test    npm run test:e2e    npm run test:cov    npm test -- --watch

4. Batafsil kod namunalari

Misol 1 — Service unit test (mock — 2.5)

ts
// 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)

ts
// 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)

ts
// 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)

ts
// 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)

ts
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)

ts
// 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)

ts
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)

ts
// 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)

ts
// 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:

ts
// 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)

ts
// 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:

ts
// 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):

yaml
# .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)

text
  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 e2e

5. To'g'ri va noto'g'ri holatlar

1) Test'siz kod

text
 "ishlaydi shekilli" (taxmin — 2.1)
 unit + e2e (isbot)

2) Real DB unit test'da

ts
//  real repository (sekin, izolyatsiya yo'q — 2.5)
providers: [UsersService, UserRepository]

//  mock
{ provide: getRepositoryToken(User), useValue: mockRepo }

3) Over-mocking

text
 hamma narsani mock (real kod tekshirilmaydi — 2.12)
 tashqi bog'liqlik mock; oddiy utility real

4) Test'lar bir-biriga bog'liq

text
 test tartibga bog'liq (biri buzilsa, kaskad — 2.10)
 izolyatsiya (beforeEach, clearAllMocks)

5) Production DB'da e2e

text
 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)

  1. Service unit test: mock repository; CRUD + edge case (Misol 1, 7, 2.5).
  2. Bog'liqlikli service: auth service (mock UsersService/JwtService/MailService — Misol 2).
  3. Controller unit test: service mock; uzatish tekshiruvi (Misol 3, 2.7).
  4. Guard test: RolesGuard (ExecutionContext mock — Misol 6, 2.11).
  5. Pipe/Interceptor test: custom pipe/interceptor (Misol 9).
  6. e2e test: CRUD oqimi (Supertest — Misol 4, 2.8).
  7. e2e auth: signup login himoyalangan (Misol 5).
  8. Izolyatsiya: beforeEach, clearAllMocks 2.10-bob.
  9. Edge case'lar: xato, null, bo'sh (Misol 7, 2.4).
  10. Fixture + faker: takroriy ma'lumotni fixture factory bilan (Misol 11, 2.11c).
  11. e2e override: overrideProvider (email mock) + overrideGuard (soxta auth — Misol 10, 2.11b).
  12. Integration test: in-memory SQLite bilan real repository (Misol 12, 2.11d).
  13. 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!
8.11-bob: Testing — Jest, unit test va e2e test (chuqur) — Wisar