Jest Framework — Testing asoslari
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\\.tsquot;,
// .spec.ts bilan tugagan barcha fayllar test deb qaraladi
"transform": {
"^.+\\.(t|j)squot;: "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 uchuntoEqual— qiymat tenglik, ob'ektlar uchuntoThrow— xato otilishini kutishtoHaveBeenCalledWith— mock qanday chaqirilganini tekshirish
Keyingi dars
54-dars: Unit Tests — NestJS + Jest