Unit Test — service va helper funksiyalarni testlash

~20 daq · Express · Testing

Muammodan boshlaylik

Supertest bilan HTTP endpointlarni testladingiz. Lekin loyihada ko'p logika endpoint dan tashqarida — service funksiyalarda.

// userService.js
export const foydalanuvchiYarat = async (data) => {
  // email mavjudligini tekshirish
  // parolni hashlash
  // DB ga yozish
  // xush kelibsiz emaili yuborish
};

Bu funksiyani Supertest bilan testlash — HTTP dan o'tib, hamma qadam ishlatiladi. Agar email yuborish sinsa — test sinadi, lekin muammo boshqa joyda edi.

Unit test — bitta narsani testlash. Faqat logika, DB va email olmaydi.


Unit test nima?

Unit — eng kichik test birligi. Bitta funksiya, bitta qaror, bitta holat.

Integration test:           Unit test:
──────────────────          ──────────────────
HTTP so'rov                 faqat funksiya
  → middleware              faqat shu logika
  → route handler           tashqi bog'liqlik yo'q
  → service
  → DB
  → email

Unit test tez ishlaydi (millisekundlarda), ko'p holat tekshirish oson.


Hayotiy o'xshatma

Avtomobil zavodi. Integratsion test — tayyor mashinani yo'lga chiqarib sinovdan o'tkazish. Unit test — faqat dvigatelni stendda tekshirish. Dvigatel to'g'ri ishlasa — mashinaga o'rnatiladi.

Har bir qism alohida tekshiriladi. Muammo qayerdaligini aniq biласиз.


Service funksiyasini ajratish

// services/userService.js
import bcrypt from 'bcrypt';
import { db } from '../db.js';
import { emailYuborish } from './emailService.js';

export const foydalanuvchiYarat = async ({ ism, email, parol }) => {
  const mavjud = await db.users.findOne({ where: { email } });
  if (mavjud) throw new Error('Bu email band');

  const hash = await bcrypt.hash(parol, 10);
  const user = await db.users.create({ ism, email, parol: hash });

  await emailYuborish(email, 'Xush kelibsiz!');

  return user;
};

Bu funksiyani unit testlash uchun — db va emailYuborish ni mock qilish kerak. Bu keyingi darsda. Hozir tashqi bog'liqliksiz funksiyalardan boshlaylik.


Sof funksiyalarni testlash

Sof funksiya — faqat kirish ma'lumotiga qarab natija qaytaradi. DB, email, vaqt yo'q.

// utils/helpers.js
export const narxFormat = (narx) => {
  return narx.toLocaleString('uz-UZ') + ' so\'m';
};

export const sahifaHisob = (jami, sahifadagi) => {
  return Math.ceil(jami / sahifadagi);
};

export const emailValidatsiya = (email) => {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
};
// utils/helpers.test.js
import { narxFormat, sahifaHisob, emailValidatsiya } from './helpers.js';

describe('narxFormat', () => {
  test('minglikni formatlaydi', () => {
    expect(narxFormat(5000000)).toBe('5 000 000 so\'m');
  });
});

describe('sahifaHisob', () => {
  test('to\'liq bo\'linsa', () => {
    expect(sahifaHisob(20, 10)).toBe(2);
  });

  test('qoldiq bo\'lsa — yuqoriga yaxlitlash', () => {
    expect(sahifaHisob(21, 10)).toBe(3);
  });

  test('sifr element', () => {
    expect(sahifaHisob(0, 10)).toBe(0);
  });
});

describe('emailValidatsiya', () => {
  test('to\'g\'ri email', () => {
    expect(emailValidatsiya('ali@example.com')).toBe(true);
  });

  test('@ yo\'q', () => {
    expect(emailValidatsiya('aliexample.com')).toBe(false);
  });

  test('domen yo\'q', () => {
    expect(emailValidatsiya('ali@')).toBe(false);
  });
});

Xato holatlari

Funksiya xato tashlasa — uni ham testlash kerak:

// utils/validator.js
export const yoshniTekshir = (yosh) => {
  if (typeof yosh !== 'number') throw new TypeError('Yosh raqam bo\'lishi kerak');
  if (yosh < 0) throw new RangeError('Yosh manfiy bo\'lishi mumkin emas');
  if (yosh > 150) throw new RangeError('Yosh 150 dan katta bo\'lishi mumkin emas');
  return true;
};
import { yoshniTekshir } from './validator.js';

describe('yoshniTekshir', () => {
  test('to\'g\'ri yosh', () => {
    expect(yoshniTekshir(25)).toBe(true);
  });

  test('raqam emas — TypeError', () => {
    expect(() => yoshniTekshir('yigirma besh')).toThrow(TypeError);
  });

  test('manfiy — RangeError', () => {
    expect(() => yoshniTekshir(-1)).toThrow('Yosh manfiy bo\'lishi mumkin emas');
  });

  test('150 dan katta — xato', () => {
    expect(() => yoshniTekshir(200)).toThrow(RangeError);
  });
});

💡 Xato testida expect(() => fn()) — funksiyani chaqirib emas, o'rab bering. Aks holda xato test ichida yuz beradi va Jest ushlayolmaydi.


Async funksiyalar

// services/postService.js
export const postniTopish = async (id) => {
  const post = await db.posts.findByPk(id);
  if (!post) throw new Error('Post topilmadi');
  return post;
};
import { postniTopish } from './postService.js';

// Async xato testlash
test('yo\'q post — xato tashlaydi', async () => {
  await expect(postniTopish(9999)).rejects.toThrow('Post topilmadi');
});

Har xil holatlarda tekshirish

Yaxshi unit test — chegaraviy qiymatlarni tekshiradi:

describe('sahifaHisob', () => {
  test('odatiy holat', () => expect(sahifaHisob(20, 10)).toBe(2));
  test('bitta element', () => expect(sahifaHisob(1, 10)).toBe(1));
  test('sifr', () => expect(sahifaHisob(0, 10)).toBe(0));
  test('sahifadan ko\'p', () => expect(sahifaHisob(100, 10)).toBe(10));
  test('qoldiq bor', () => expect(sahifaHisob(11, 10)).toBe(2));
});

Chegaraviy qiymatlar — eng ko'p xato chiqadigan joylar: 0, 1, -1, null, bo'sh massiv.


Tez-tez uchraydigan xatolar

1. Juda katta funksiyani unit testlash

// ❌ — bu unit test emas, integration test
test('foydalanuvchiYarat barcha narsani qiladi', async () => {
  // DB, email, bcrypt — hammasi bir testda
});

// ✅ — logikani ajrating, alohida testlang
test('email format to\'g\'ri', () => { ... });
test('parol kamida 8 belgi', () => { ... });

2. Test nomi noaniq

// ❌
test('test 1', () => { ... });

// ✅ — nima tekshirilayotgani aniq
test('manfiy yosh qabul qilinmaydi', () => { ... });

3. Bitta testda bir nechta tekshiruv

// ❌ — qaysi tekshiruv sindi?
test('barcha holat', () => {
  expect(fn(1)).toBe(true);
  expect(fn(-1)).toBe(false);
  expect(fn(0)).toBe(false);
});

// ✅ — har holat alohida test
test('musbat son', () => expect(fn(1)).toBe(true));
test('manfiy son', () => expect(fn(-1)).toBe(false));
test('nol', () => expect(fn(0)).toBe(false));

Xulosa

  • Unit test — bitta funksiya, bitta holat, tashqi bog'liqlik yo'q
  • Sof funksiyalar — eng oson testlanadi: kirish → kutilgan chiqish
  • Xato testida: expect(() => fn()) — funksiyani o'rang
  • Async xato: await expect(fn()).rejects.toThrow(...)
  • Chegaraviy qiymatlarni tekshiring: 0, null, bo'sh, manfiy

Keyingi dars: 53-dars: Integration Test — database bilan testlash