Testing — Jest bilan JavaScript testlash
Muammodan boshlaylik
export function chegirmaHisobla(narx, foiz) {
return narx - narx * foiz / 100;
}
Funksiyani brauzerda sinab ko'rdingiz — ishlaydi. Bir oydan keyin kimdir uni "yaxshiladi": foizni 0–1 oralig'ida qabul qiladigan qildi. Endi 20% chegirma 0.2% ga aylandi, va buni foydalanuvchilar bir hafta o'tib payqadi.
Har bir o'zgarishdan keyin hamma narsani qo'lda tekshirib bo'lmaydi. Kerak — kodni avtomatik tekshiradigan kod: testlar.
Jest o'rnatish
npm install -D jest
{
"type": "module",
"scripts": {
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
}
}
[!INFO] Jest ES modullarni (
import/export) hali eksperimental rejimda qo'llab-quvvatlaydi, shuning uchun yuqoridagi flag kerak. Vite loyihalarida Vitest ko'proq qulay: API si Jest bilan deyarli bir xil (describe,test,expect), lekin sozlashsiz ES modullar va Vite konfiguratsiyasi bilan ishlaydi —npm i -D vitestva"test": "vitest". Bu darsdagi barcha misollar ikkalasida ham ishlaydi (Vitest daimport { describe, test, expect, vi } from 'vitest').
Birinchi test
// src/narx.js
export function chegirmaHisobla(narx, foiz) {
if (foiz < 0 || foiz > 100) throw new RangeError('Foiz 0 va 100 orasida bo\'lishi kerak');
return narx - (narx * foiz) / 100;
}
// src/narx.test.js
import { chegirmaHisobla } from './narx.js';
describe('chegirmaHisobla', () => {
test('20% chegirma', () => {
expect(chegirmaHisobla(100000, 20)).toBe(80000);
});
test('0% — narx o\'zgarmaydi', () => {
expect(chegirmaHisobla(50000, 0)).toBe(50000);
});
test('noto\'g\'ri foiz — xato', () => {
expect(() => chegirmaHisobla(100, 150)).toThrow(RangeError);
});
});
npm test
PASS src/narx.test.js
chegirmaHisobla
✓ 20% chegirma (2 ms)
✓ 0% — narx o'zgarmaydi
✓ noto'g'ri foiz — xato (1 ms)
Tests: 3 passed, 3 total
Endi kimdir formulani buzsa — test darhol qizil bo'ladi.
Test tuzilishi — AAA
test('savatga mahsulot qo\'shish', () => {
// Arrange — tayyorlash
const savat = [];
const mahsulot = { id: 1, narx: 5000 };
// Act — bajarish
const natija = savatgaQosh(savat, mahsulot);
// Assert — tekshirish
expect(natija).toHaveLength(1);
expect(savat).toHaveLength(0); // asl massiv o'zgarmadi
});
Matcher lar
| Matcher | Tekshiradi |
|---|---|
toBe(x) |
Qat'iy tenglik (Object.is) — primitivlar |
toEqual(x) |
Chuqur tenglik — obyekt, massiv |
toBeTruthy() / toBeFalsy() |
Rost/yolg'on qiymat |
toBeNull() / toBeUndefined() |
null / undefined |
toContain(x) |
Massiv elementi yoki satr qismi |
toHaveLength(n) |
Uzunlik |
toMatch(/re/) |
Satr regex ga mos |
toThrow() |
Funksiya xato tashlaydi |
toBeCloseTo(x) |
Kasr sonlar (0.1 + 0.2) |
.not |
Inkor: expect(x).not.toBe(5) |
expect({ ism: 'Ali', yosh: 25 }).toEqual({ ism: 'Ali', yosh: 25 }); // ✅
expect({ ism: 'Ali' }).toBe({ ism: 'Ali' }); // ❌ har xil obyekt
expect(0.1 + 0.2).toBeCloseTo(0.3); // ✅
Asinxron kod
// src/foydalanuvchi.js
export async function ismniOl(id, fetchFn = fetch) {
const res = await fetchFn(`/api/users/${id}`);
if (!res.ok) throw new Error('Topilmadi');
const user = await res.json();
return user.ism;
}
test('ismni qaytaradi', async () => {
const soxtaFetch = async () => ({ ok: true, json: async () => ({ ism: 'Lola' }) });
await expect(ismniOl(1, soxtaFetch)).resolves.toBe('Lola');
});
test('404 da xato', async () => {
const soxtaFetch = async () => ({ ok: false });
await expect(ismniOl(1, soxtaFetch)).rejects.toThrow('Topilmadi');
});
fetch ni parametr qilib berish — tashqi bog'liqlikni testda oson almashtirish usuli (dependency injection).
Mock funksiyalar
import { jest } from '@jest/globals';
test('har bir element uchun callback chaqiriladi', () => {
const callback = jest.fn();
[1, 2, 3].forEach(callback);
expect(callback).toHaveBeenCalledTimes(3);
expect(callback).toHaveBeenCalledWith(2, 1, [1, 2, 3]);
});
test('qaytariladigan qiymat', () => {
const narxOl = jest.fn().mockReturnValue(1000);
expect(narxOl()).toBe(1000);
});
Vaqt — soxta taymerlar
test('debounce 300 ms dan keyin chaqiradi', () => {
jest.useFakeTimers();
const fn = jest.fn();
const d = debounce(fn, 300);
d(); d(); d();
expect(fn).not.toHaveBeenCalled();
jest.advanceTimersByTime(300);
expect(fn).toHaveBeenCalledTimes(1);
jest.useRealTimers();
});
beforeEach va izolyatsiya
describe('savat store', () => {
let store;
beforeEach(() => {
store = createStore({ mahsulotlar: [] }); // har test — toza holat
});
test('qo\'shish', () => { /* ... */ });
test('o\'chirish', () => { /* ... */ });
});
Testlar bir-biriga bog'liq bo'lmasligi kerak — har biri istalgan tartibda, yolg'iz o'zi ishlashi shart.
Nimani test qilish
- ✅ Biznes mantiq: narx, chegirma, validatsiya, formatlash
- ✅ Chegaraviy holatlar: bo'sh massiv,
0, manfiy son,null - ✅ Xato holatlari: noto'g'ri kirish, server xatosi
- ❌ Kutubxonaning o'zi (
Array.prototype.mapishlaydimi?) - ❌ Ichki tafsilotlar — natijani tekshiring, qanday hisoblanganini emas
[!TIP]
npm test -- --coverage— qaysi qatorlar test qilinmaganini ko'rsatadi. 100% qamrov maqsad emas; muhim mantiq va xatolar tez-tez chiqadigan joylar qamralgan bo'lsin.
Nima uchun shunday?
Nima uchun test yozishga vaqt sarflash kerak — bu vaqtda yangi funksiya yozsa bo'lmaydimi?
Chunki testlar kodni o'zgartirishni xavfsiz qiladi. Testlarsiz har bir o'zgarish — tavakkal: biror joyni buzganingizni foydalanuvchi aytguncha bilmaysiz. Shuning uchun testsiz loyihalarda dasturchilar eski kodga tegishdan qo'rqishadi va u yillar davomida chirib boradi. Testlar bilan esa siz bemalol qayta yozasiz (refactoring): agar testlar yashil bo'lsa — hamma narsa ishlaydi. Bundan tashqari, test — eng aniq hujjat: u funksiya nima qilishi kerakligini ishlaydigan misollar bilan ko'rsatadi.
Xulosa
Eslab qoling:
- Test — kodni avtomatik tekshiradigan kod; regressiyalardan himoya
- Jest yoki Vitest:
describe,test,expect; fayllar*.test.js - AAA: Arrange, Act, Assert
toBe— primitivlar,toEqual— obyektlar,toThrow,toBeCloseTo- Async:
asynctest,resolves/rejects jest.fn()— mock,useFakeTimers— vaqt; bog'liqliklarni parametr qilib bering- Testlar mustaqil bo'lsin —
beforeEachbilan toza holat
Keyingi dars
82-dars: Debugging — brauzer DevTools da xatolarni topish