Testing — Jest bilan JavaScript testlash

~30 daq · JavaScript · Amaliy va Professional mavzular

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 vitest va "test": "vitest". Bu darsdagi barcha misollar ikkalasida ham ishlaydi (Vitest da import { 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.map ishlaydimi?)
  • ❌ 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: async test, resolves / rejects
  • jest.fn() — mock, useFakeTimers — vaqt; bog'liqliklarni parametr qilib bering
  • Testlar mustaqil bo'lsin — beforeEach bilan toza holat

Keyingi dars

82-dars: Debugging — brauzer DevTools da xatolarni topish