Jest Framework — Testing asoslari

~20 daq · NestJS · Dokumentatsiya va Testing

Swagger va Jest — farqi nima?

Oldingi darsda Swagger o'rgandingiz. Endi Jest. Ikkalasi ham "test" so'ziga aloqador ko'rinadi — lekin aslida butunlay boshqa narsalar.

  Swagger                              Jest
  ────────────────────────────────────────────────────────────
  Maqsad:   API ni KO'RSATISH         Kodning TO'G'RI ishlashini
            va sinab ko'rish          AVTOMATIK tekshirish

  Kim uchun: Frontend dasturchi,       Siz o'zingiz uchun
             QA muhandis,              (va jamoa uchun)
             yangi dasturchi

  Qachon:   Server ishlab turganida   Kod yozilganda va
            brauzerdan foydalaniladi  CI/CD da avtomatik

  Natija:   Vizual interfeys —        Terminal — testlar
            endpoint larni ko'rish    o'tdi yoki o'tmadi

  Xato      Inson o'zi sinab          Kod o'zi o'zini
  topish:   ko'rib topadi             tekshirib topadi

Swagger — hujjat va qo'lda sinash vositasi. Jest — avtomatik tekshirish vositasi.

Real loyihada ikkalasi birga ishlatiladi:

  • Swagger → Frontend dasturchi API ni tushunadi
  • Jest → Har commit da 200+ test avtomatik ishlaydi, xato bo'lsa deploy to'xtaydi

Muammodan boshlaylik

Siz calculateDiscount funksiya yozdingiz:

function calculateDiscount(price: number, percent: number): number {
  return price - (price * percent / 100);
}

Ishlayaptimi? Siz: "Ha, men sinab ko'rdim — 1000 ga 10% chegirma 900 chiqdi."

Bir oy o'tdi. Boshqa dasturchi bu funksiyani o'zgartirdi — percent / 100 o'rniga percent / 10 qilib qo'ydi (tasodifan). Xato. Lekin hech kim sezmadi.

Uch oy o'tdi. Mijoz: "Chegirma noto'g'ri hisoblanayapti!"

  ❌ Muammo:
  Har o'zgarishdan keyin barcha funksiyalarni qo'lda sinab chiqish
  — ko'p vaqt, odam xatosi, va ko'pincha umuman qilinmaydi.

Jest yechimi:

// calculateDiscount.spec.ts
test('1000 so\'mga 10% chegirma 900 bo\'lishi kerak', () => {
  expect(calculateDiscount(1000, 10)).toBe(900);
});

test('500 so\'mga 50% chegirma 250 bo\'lishi kerak', () => {
  expect(calculateDiscount(500, 50)).toBe(250);
});

test('chegirma 0% bo\'lsa narx o\'zgarmaydi', () => {
  expect(calculateDiscount(1000, 0)).toBe(1000);
});

Endi kimdir funksiyani o'zgartirsa — npm test da:

  ✕ 1000 so'mga 10% chegirma 900 bo'lishi kerak
    Expected: 900
    Received: 900.0  ← yoki boshqa xato

  Xato topildi. Deploy to'xtatildi. Mijozga yetib bormadi.

Jest nima?

Jest — JavaScript va TypeScript uchun eng mashhur test framework. Meta (Facebook) tomonidan yaratilgan.

NestJS loyiha yaratilganda Jest avtomatik o'rnatiladi — qo'shimcha o'rnatish shart emas.

# Testlarni ishga tushirish
npm test              # bir marta ishga tushiradi
npm run test:watch    # fayllar o'zgarganda qayta ishga tushiradi
npm run test:cov      # qancha kod qamralgani (coverage) ko'rsatadi

Test fayl qayerda bo'ladi?

NestJS da test fayllar ikki joyda bo'lishi mumkin:

  src/
  ├── users/
  │   ├── users.service.ts
  │   ├── users.service.spec.ts    ← Unit test (service yonida)
  │   ├── users.controller.ts
  │   └── users.controller.spec.ts ← Unit test (controller yonida)
  └── test/
      └── app.e2e-spec.ts          ← E2E test (alohida papkada)

.spec.ts — "specification" (texnik tavsif). Test fayllar shu kengaytma bilan yoziladi.


Jest ning asosiy tuzilishi

// Eng oddiy test fayli
describe('Guruh nomi', () => {
  //  describe — testlarni mantiqiy guruhlaydi

  it('nima bo\'lishi kerakligini yozasiz', () => {
    // it yoki test — bitta test holati
    // Ichida: nima kutilayotgani tekshiriladi
  });

});

Haqiqiy misol:

// math.spec.ts
describe('Matematika yordamchi funksiyalari', () => {

  describe('calculateDiscount', () => {

    it('to\'g\'ri chegirma hisoблайди', () => {
      const natija = calculateDiscount(1000, 10);
      expect(natija).toBe(900);
      //    ──────   ──────────
      //    nima     qanday bo'lishi kerak
    });

    it('chegirma 0 bo\'lsa narx o\'zgarmaydi', () => {
      expect(calculateDiscount(500, 0)).toBe(500);
    });

    it('100% chegirmada narx 0 bo\'ladi', () => {
      expect(calculateDiscount(800, 100)).toBe(0);
    });

  });

});
  Natija:

  Matematika yordamchi funksiyalari
    calculateDiscount
      ✓ to'g'ri chegirma hisoблайди          (2ms)
      ✓ chegirma 0 bo'lsa narx o'zgarmaydi   (1ms)
      ✓ 100% chegirmada narx 0 bo'ladi       (1ms)

  Tests: 3 passed

expect() va matcher lar

expect() — "kutaman" degani. Uning yoniga matcher (taqqoslagich) yoziladi.

Teng-tengmas

expect(2 + 2).toBe(4);
// toBe — qat'iy tenglik (===)
// Primitiv qiymatlar uchun: son, string, boolean

expect({ ism: 'Ali' }).toEqual({ ism: 'Ali' });
// toEqual — qiymat tenglik (ob'ekt ichidagi qiymatlarni solishtiradi)
// toBe ob'ektlar uchun ishlamaydi — chunki { } === { } → false (turli xotira)

expect(1 + 1).not.toBe(3);
// not — teskarisi. "3 bo'lmasligi kerak"

Mavjudlik

expect(null).toBeNull();
expect(undefined).toBeUndefined();
expect('salom').toBeDefined();     // undefined emas
expect(1).toBeTruthy();            // true kabi qiymat
expect(0).toBeFalsy();             // false kabi qiymat

Sonlar

expect(10).toBeGreaterThan(5);        // 10 > 5
expect(10).toBeGreaterThanOrEqual(10);// 10 >= 10
expect(3).toBeLessThan(5);            // 3 < 5
expect(0.1 + 0.2).toBeCloseTo(0.3);  // float tenglik (0.1+0.2 = 0.30000000000000004)

Stringlar

expect('Salom dunyo').toContain('dunyo');
// Ichida bor-yo'qligini tekshiradi

expect('ali@example.com').toMatch(/@/);
// RegExp bilan tekshiradi

Massivlar

expect([1, 2, 3]).toContain(2);
// Massivda element bor-yo'qligini tekshiradi

expect([1, 2, 3]).toHaveLength(3);
// Uzunlikni tekshiradi

expect([1, 2, 3]).toEqual(expect.arrayContaining([1, 3]));
// Massiv [1, 3] ni o'z ichiga oladi (tartib muhim emas)

Xatolar

function nolGaBo'lish(son: number) {
  if (son === 0) throw new Error('Nolga bo\'lish mumkin emas');
  return 10 / son;
}

expect(() => nolGaBo'lish(0)).toThrow();
// Xato otishini kutadi

expect(() => nolGaBo'lish(0)).toThrow('Nolga bo\'lish mumkin emas');
// Aniq xato xabari bilan

expect(() => nolGaBo'lish(0)).toThrow(Error);
// Xato turi bilan

Setup va Teardown — tayyorgarlik va tozalash

Ba'zan har testdan oldin bir narsa qilish kerak (ma'lumotlar tayyorlash), har testdan keyin tozalash kerak.

describe('UserService testlari', () => {

  let service: UsersService;
  let foydalanuvchilar: User[];

  // Har describe bloki oldidan — bir marta
  beforeAll(() => {
    console.log('Barcha testlar boshlandi');
  });

  // Har test oldidan — har safar
  beforeEach(() => {
    service = new UsersService();
    foydalanuvchilar = [
      { id: 1, name: 'Ali',  email: 'ali@test.com'  },
      { id: 2, name: 'Vali', email: 'vali@test.com' },
    ];
    // Har test uchun yangi ma'lumotlar — testlar bir-biriga ta'sir qilmasin
  });

  // Har test tugagandan keyin — har safar
  afterEach(() => {
    jest.clearAllMocks();
    // Mock lar tozalansin
  });

  // Barcha testlar tugagandan keyin — bir marta
  afterAll(() => {
    console.log('Barcha testlar tugadi');
  });

  it('foydalanuvchilar ro\'yxati bo\'sh emas', () => {
    expect(foydalanuvchilar).toHaveLength(2);
  });

});
  beforeAll  → bitta marta, barchadan oldin
  beforeEach → har test oldidan
  afterEach  → har test keyin
  afterAll   → bitta marta, barchadan keyin

Mock — tashqi bog'liqliklarni almashtirish

Mock — haqiqiy funksiya yoki ob'ekt o'rniga "soxtasi" qo'yish. Nima uchun kerak?

Test yozayapsiz. UsersService.create() bazaga yozadi, email yuboradi. Test da baza yoki email server bo'lmasligi mumkin. Bundan tashqari, test tez ishlashi kerak — baza so'rovi sekin.

Yechim: baza va email servisni mock qilish — haqiqiy emas, lekin xuddi o'sha kabi javob beradigan "soxta" versiya.

jest.fn() — oddiy mock funksiya

const mockFn = jest.fn();
// Bo'sh mock funksiya

mockFn('salom');
mockFn('dunyo');

expect(mockFn).toHaveBeenCalled();
// Chaqirilganmi?

expect(mockFn).toHaveBeenCalledTimes(2);
// Necha marta chaqirilgan?

expect(mockFn).toHaveBeenCalledWith('salom');
// Qanday argument bilan chaqirilgan?

mockReturnValue — qaytarish qiymati

const getUserMock = jest.fn();

getUserMock.mockReturnValue({ id: 1, name: 'Ali' });
// Bu mock chaqirilganda { id: 1, name: 'Ali' } qaytaradi

const user = getUserMock();
expect(user.name).toBe('Ali');

mockResolvedValue — async mock

const findUserMock = jest.fn();

findUserMock.mockResolvedValue({ id: 1, name: 'Ali' });
// Promise qaytaradi — async funksiya uchun

const user = await findUserMock();
expect(user.name).toBe('Ali');

mockRejectedValue — xato qaytarish

const createUserMock = jest.fn();

createUserMock.mockRejectedValue(new Error('Email band'));
// Xato otadi

await expect(createUserMock()).rejects.toThrow('Email band');

NestJS da birinchi test

NestJS da UsersService ni test qilamiz. Bu unit test — faqat service ni test qilamiz, boshqa narsalar mock.

// users/users.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { UsersService } from './users.service';
import { getRepositoryToken } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
import { Repository } from 'typeorm';
import { ConflictException, NotFoundException } from '@nestjs/common';

describe('UsersService', () => {
  let service: UsersService;
  let repository: jest.Mocked<Repository<User>>;
  // jest.Mocked — barcha metodlari mock bo'lgan tip

  beforeEach(async () => {
    // NestJS test moduli — haqiqiy modul o'rniga test uchun
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        UsersService,
        {
          provide: getRepositoryToken(User),
          // "User repository kerak bo'lsa — quyidagini ber"
          useValue: {
            findOne:      jest.fn(),
            create:       jest.fn(),
            save:         jest.fn(),
            find:         jest.fn(),
            remove:       jest.fn(),
            findAndCount: jest.fn(),
          },
          // Haqiqiy baza o'rniga mock metodlar
        },
      ],
    }).compile();

    service    = module.get<UsersService>(UsersService);
    repository = module.get(getRepositoryToken(User));
  });

  afterEach(() => jest.clearAllMocks());

  // ─── CREATE ──────────────────────────────────────────

  describe('create', () => {

    it('yangi foydalanuvchi yaratadi', async () => {
      // ARRANGE — tayyorgarlik
      const dto = { name: 'Ali', email: 'ali@test.com', password: '12345678' };

      repository.findOne.mockResolvedValue(null);
      // "Bu email baza da yo'q" — null qaytaradi

      const yaratilganUser = { id: 1, ...dto, password: 'hashed' };
      repository.create.mockReturnValue(yaratilganUser as User);
      repository.save.mockResolvedValue(yaratilganUser as User);

      // ACT — ishga tushirish
      const natija = await service.create(dto);

      // ASSERT — tekshirish
      expect(natija).toEqual(yaratilganUser);
      expect(repository.findOne).toHaveBeenCalledWith({
        where: { email: dto.email },
      });
      expect(repository.save).toHaveBeenCalledTimes(1);
    });

    it('email band bo\'lsa ConflictException otadi', async () => {
      // ARRANGE
      const dto = { name: 'Ali', email: 'ali@test.com', password: '12345678' };

      repository.findOne.mockResolvedValue({ id: 1, email: dto.email } as User);
      // "Bu email baza da BOR" — user qaytaradi

      // ACT + ASSERT
      await expect(service.create(dto))
        .rejects.toThrow(ConflictException);
      // ConflictException otilishi kerak
    });

  });

  // ─── FIND ONE ────────────────────────────────────────

  describe('findOne', () => {

    it('mavjud foydalanuvchini qaytaradi', async () => {
      const user = { id: 1, name: 'Ali', email: 'ali@test.com' } as User;
      repository.findOne.mockResolvedValue(user);

      const natija = await service.findOne(1);

      expect(natija).toEqual(user);
      expect(repository.findOne).toHaveBeenCalledWith({ where: { id: 1 } });
    });

    it('foydalanuvchi topilmasa NotFoundException otadi', async () => {
      repository.findOne.mockResolvedValue(null);

      await expect(service.findOne(999))
        .rejects.toThrow(NotFoundException);
    });

  });

});

AAA pattern — test yozish tartibi

Yaxshi test uchta qismdan iborat:

  ARRANGE  →  ACT  →  ASSERT
  ─────────────────────────────────────────
  Tayyorla     Ishga    Natijani
               tushir   tekshir
it('foydalanuvchi yangilanadi', async () => {

  // ── ARRANGE — ma'lumotlar va mock lar ──
  const mavjudUser = { id: 1, name: 'Ali', email: 'ali@test.com' } as User;
  const dto = { name: 'Ali Valiyev' };

  repository.findOne.mockResolvedValue(mavjudUser);
  repository.save.mockResolvedValue({ ...mavjudUser, ...dto });

  // ── ACT — funksiya chaqiriladi ──
  const natija = await service.update(1, dto);

  // ── ASSERT — natija tekshiriladi ──
  expect(natija.name).toBe('Ali Valiyev');
  expect(repository.save).toHaveBeenCalledTimes(1);
});

Bu tartibda yozilgan testlar o'qilishi oson — kim o'qisa ham nima bo'layotganini tushunadi.


Test coverage — qancha kod qamralgan?

npm run test:cov
  ──────────────────────────────────────────────────────────
  File              │ % Stmts │ % Branch │ % Funcs │ % Lines
  ──────────────────────────────────────────────────────────
  users/
    users.service   │   85.71 │   75.00  │   83.33  │   87.50
    users.controller│   60.00 │   50.00  │   60.00  │   62.50
  ──────────────────────────────────────────────────────────
  All files         │   73.68 │   63.63  │   72.72  │   75.00
  • Stmts — qancha kod qatori (statement) ishga tushirildi
  • Branch — if/else shartlarning qancha tarmog'i tekshirildi
  • Funcs — qancha funksiya chaqirildi
  • Lines — qancha qator qamraldi

💡 100% coverage kerak emas. Real loyihalarda 70-80% yaxshi ko'rsatkich. Muhim biznes logika — 100% bo'lishi kerak.


.skip va .only — testlarni boshqarish

// Faqat shu testni ishga tushir — qolganlarni o'tkazib yuboradi
it.only('faqat shu ishlaydi', () => {
  expect(1 + 1).toBe(2);
});

// Shu testni o'tkazib yuboradi
it.skip('hozircha o\'tkazib yuboriladi', () => {
  // ishlamaydi
});

// describe.only va describe.skip ham ishlaydi
describe.only('faqat shu guruh', () => { ... });
describe.skip('bu guruh o\'tkaziladi', () => { ... });

only — bitta narsani debug qilayotganda. skip — hozircha buzilgan test ni vaqtinchalik o'tkazib yuborish.


Jest konfiguratsiyasi — NestJS da

NestJS da package.json da Jest konfiguratsiyasi allaqachon bor:

// package.json
{
  "jest": {
    "moduleFileExtensions": ["js", "json", "ts"],
    "rootDir": "src",
    "testRegex": ".*\\.spec\\.ts
quot;, // .spec.ts bilan tugagan barcha fayllar test deb qaraladi "transform": { "^.+\\.(t|j)s
quot;: "ts-jest" }, "collectCoverageFrom": ["**/*.(t|j)s"], "coverageDirectory": "../coverage", "testEnvironment": "node" } }

Swagger vs Jest — qachon nima?

  Vaziyat                                    Qaysi vosita?
  ────────────────────────────────────────────────────────────
  Frontend dasturchi API ni tushunishi kerak   Swagger
  API ni brauzerdan sinab ko'rish              Swagger
  Funksiya to'g'ri ishlashini tekshirish       Jest
  Refaktoringdan keyin hech narsa buzilmaganmi Jest
  CI/CD da avtomatik tekshirish               Jest
  Boshqa dasturchi API ni ko'rishi             Swagger
  Muhim biznes logikani himoya qilish          Jest

Vazifalar

1-vazifa — Matcher lar: Quyidagi funksiyani test qiling. Kamida 5 xil holatni tekshiring:

function gradeToLetter(grade: number): string {
  if (grade >= 90) return 'A';
  if (grade >= 80) return 'B';
  if (grade >= 70) return 'C';
  if (grade >= 60) return 'D';
  return 'F';
}

2-vazifa — Mock: EmailService ni mock qilib UsersService.create() ni test qiling. create chaqirilganda emailService.sendWelcome() ham chaqirilganligini tekshiring.

3-vazifa — O'ylash: Test yozmasdan npm run test:cov ishga tushirsangiz coverage 0% bo'ladi. 70% coverage ga yetish uchun qaysi fayllardan boshlash mantiqan to'g'ri? Nima uchun?


Xulosa

Jest nima: Avtomatik test framework. Kod o'zini o'zi tekshiradi.

Swagger bilan farqi:

  • Swagger → vizual hujjat, qo'lda sinash
  • Jest → avtomatik tekshirish, CI/CD da ishlaydi

Asosiy tushunchalar:

Tushuncha Ma'nosi
describe Testlarni guruhga ajratish
it / test Bitta test holati
expect().toBe() Natijani tekshirish
beforeEach Har test oldidan tayyorgarlik
jest.fn() Mock funksiya yaratish
mockResolvedValue Async mock qaytarish qiymati
AAA pattern Arrange → Act → Assert

Matcher lar:

  • toBe — qat'iy tenglik (===), primitivlar uchun
  • toEqual — qiymat tenglik, ob'ektlar uchun
  • toThrow — xato otilishini kutish
  • toHaveBeenCalledWith — mock qanday chaqirilganini tekshirish

Keyingi dars

54-dars: Unit Tests — NestJS + Jest