Testlash (Jest)

Async testlar va mock

Real dasturlar tarmoq so'rovlari, ma'lumotlar bazasi va tashqi xizmatlar bilan ishlaydi. Bularni testda to'g'ri tekshirish uchun asinxron testlar va mock (soxta) obyektlarni bilish shart. Bu darsda aynan shularni o'rganamiz.

Asinxron kodni testlash muammosi

Agar test ichida promise bo'lsa, lekin siz uni kutmasangiz, Jest testni tugadi deb hisoblaydi va natija kelishidan oldin yashil ko'rsatadi. Bu xavfli โ€” test aslida hech narsani tekshirmagan bo'lishi mumkin:

// XATO: bu test har doim o'tadi, hatto natija noto'g'ri bo'lsa ham!
test('foydalanuvchini oladi', () => {
  foydalanuvchiOl(1).then(user => {
    expect(user.ism).toBe('Ali'); // Jest buni kutmaydi
  });
});
Asinxron testda promise'ni return qilishni yoki async/await ishlatishni unutmang. Aks holda Jest natijani kutmasdan testni tugatadi va soxta yashil natija chiqadi.

async/await bilan testlash

Eng toza yo'l โ€” test funksiyasini async qilish va promise'ni await bilan kutish:

async function foydalanuvchiOl(id) {
  // aslida bu yerda tarmoq so'rovi bo'ladi
  return { id, ism: 'Ali' };
}

test('foydalanuvchini id bo'yicha oladi', async () => {
  const user = await foydalanuvchiOl(1);
  expect(user.ism).toBe('Ali');
  expect(user.id).toBe(1);
});

Bu yerda await promise hal bo'lishini kutadi, so'ng tekshiruvlar bajariladi. Jest async funksiya qaytargan promise'ni avtomatik kutadi.

resolves va rejects

Promise'ni awaitsiz to'g'ridan-to'g'ri tekshirishning qisqa yo'li ham bor: resolves muvaffaqiyatli natijani, rejects esa xatoni tekshiradi:

// Promise muvaffaqiyatli hal bo'lishini tekshirish:
test('foydalanuvchi obyektini qaytaradi', async () => {
  await expect(foydalanuvchiOl(1)).resolves.toEqual({ id: 1, ism: 'Ali' });
});

// Promise xato bilan rad etilishini tekshirish:
async function xatoOl() {
  throw new Error('Topilmadi');
}

test('mavjud bo'lmagan foydalanuvchi uchun xato beradi', async () => {
  await expect(xatoOl()).rejects.toThrow('Topilmadi');
});
resolves va rejects oldida await qo'yishni unutmang. Bularsiz Jest promise natijasini kutmaydi.

Mock funksiya: jest.fn()

Mock (soxta) funksiya โ€” bu chaqiruvlarni yozib boradigan va siz belgilagan qiymatni qaytaradigan sun'iy funksiya. U jest.fn() bilan yaratiladi. Mocklar ikki holatda juda foydali:

test('mock funksiya chaqiruvlarni yozadi', () => {
  const mock = jest.fn();

  mock('a');
  mock('b', 42);

  // Chaqirilganini tekshirish:
  expect(mock).toHaveBeenCalled();
  expect(mock).toHaveBeenCalledTimes(2);
  expect(mock).toHaveBeenCalledWith('a');
  expect(mock).toHaveBeenCalledWith('b', 42);
});

Mock qanday qiymat qaytarishini ham belgilash mumkin:

const olMock = jest.fn();

olMock.mockReturnValue(10);          // har safar 10 qaytaradi
olMock.mockReturnValueOnce(5);       // faqat birinchi chaqiruvda 5
olMock.mockResolvedValue({ ok: 1 }); // promise sifatida hal bo'ladi

expect(olMock()).toBe(5);  // birinchisi
expect(olMock()).toBe(10); // keyingilari

Modulni mock qilish: jest.mock()

Ko'pincha butun modulni soxtalashtirish kerak bo'ladi โ€” masalan, testda real tarmoq so'rovi yubormaslik uchun HTTP kutubxonasini almashtiramiz. Buni jest.mock() qiladi. Aytaylik, bizda foydalanuvchini serverdan oladigan xizmat bor:

// apiClient.js
async function get(url) {
  // aslida real tarmoq so'rovi
  const javob = await fetch(url);
  return javob.json();
}
module.exports = { get };
// foydalanuvchiXizmati.js
const apiClient = require('./apiClient');

async function foydalanuvchiIsmi(id) {
  const user = await apiClient.get('/users/' + id);
  return user.ism;
}
module.exports = { foydalanuvchiIsmi };

Testda apiClient modulini butunlay mock qilamiz, shunda real tarmoqqa chiqmaydi:

// foydalanuvchiXizmati.test.js
const apiClient = require('./apiClient');
const { foydalanuvchiIsmi } = require('./foydalanuvchiXizmati');

jest.mock('./apiClient'); // butun modul avtomatik mock bo'ladi

test('foydalanuvchi ismini qaytaradi', async () => {
  // get soxta funksiyasi nima qaytarishini belgilaymiz:
  apiClient.get.mockResolvedValue({ ism: 'Vali' });

  const ism = await foydalanuvchiIsmi(7);

  expect(ism).toBe('Vali');
  expect(apiClient.get).toHaveBeenCalledWith('/users/7');
});
jest.mock('./apiClient') chaqirilganda Jest moduldagi barcha funksiyalarni avtomatik jest.fn()ga aylantiradi. So'ng test ichida har biriga qaytariladigan qiymatni bering.

beforeEach va afterEach

Har bir testdan oldin yoki keyin bir xil tayyorgarlik yoki tozalash kerak bo'ladi. Buning uchun hooklar ishlatiladi:

describe('savat', () => {
  let savat;

  beforeEach(() => {
    // har bir testdan oldin toza savat yaratamiz
    savat = [];
  });

  afterEach(() => {
    // mock chaqiruvlar tarixini tozalaymiz
    jest.clearAllMocks();
  });

  test('mahsulot qo'shadi', () => {
    savat.push('olma');
    expect(savat).toHaveLength(1);
  });

  test('yangi test toza savatdan boshlanadi', () => {
    expect(savat).toHaveLength(0); // beforeEach uni tozaladi
  });
});
Mocklar testlar orasida holatini saqlaydi. Buni tozalamasangiz, bir test boshqasiga ta'sir qilishi mumkin. afterEachda jest.clearAllMocks() chaqirish โ€” yaxshi odat.

Spy โ€” haqiqiy funksiyani kuzatish

Ba'zan funksiyani butunlay almashtirmasdan, faqat kuzatmoqchi bo'lasiz: u chaqirildimi, qanday argument bilan? Bunda jest.spyOn ishlatiladi. Spy asl funksiyani saqlab qoladi, lekin chaqiruvlarni yozib boradi:

const kalkulyator = {
  qoshish(a, b) {
    return a + b;
  }
};

test('qoshish metodi chaqirilganini kuzatadi', () => {
  const spy = jest.spyOn(kalkulyator, 'qoshish');

  const natija = kalkulyator.qoshish(2, 3);

  expect(natija).toBe(5); // asl funksiya baribir ishladi
  expect(spy).toHaveBeenCalledWith(2, 3);

  spy.mockRestore(); // asl holatga qaytaramiz
});
Spy'ni mockRestore() bilan tiklashni unutmang, aks holda kuzatuv keyingi testlarga ta'sir qiladi. Kerak bo'lsa spy'ni mockImplementation() bilan vaqtincha boshqacha ishlashga majburlash ham mumkin.

Xulosa