Mock — funksiya, modul, database mock qilish

~20 daq · Express · Testing

Muammodan boshlaylik

foydalanuvchiYarat funksiyasini unit testlamoqchisiz. Lekin u DB ga yozadi va email yuboradi.

Unit testda DB va email bo'lmaydi — ular tashqi tizimlar. Test uchun real DB kerak bo'lmasligi kerak. Email yuborilmasligi kerak.

Lekin funksiya ichida DB va email bor. Qanday testlaysiz?


Mock nima?

Mock — haqiqiy funksiya o'rniga qo'yiladigan "soxta" funksiya. Haqiqiy ishni bajarmasligi — faqat "ha, chaqirildi, mana natija" deyishi kerak.

Haqiqiy:                  Mock:
─────────────────         ─────────────────
emailYuborish()           emailYuborish()
  → SMTP ga ulanish         → "yuborildi" deydi
  → email yuborish          → hech narsa qilmaydi
  → 3 sekund kutish         → 0ms

Hayotiy o'xshatma

Aktyorlar uchun "dublyaj". Film tushirishda asl uchuvchi kerak emas — dublyyor uchadi. Asosiy aktyor "uchayapti" deb o'ynaydi. Test ham shunday: asosiy funksiya email "yubordi" deb hisoblaydi, aslida hech narsa ketmaydi.


jest.fn() — oddiy mock

const emailYuborish = jest.fn(); // ← soxta funksiya

emailYuborish('ali@test.com', 'Salom');

expect(emailYuborish).toHaveBeenCalled();                        // chaqirildimi?
expect(emailYuborish).toHaveBeenCalledTimes(1);                  // necha marta?
expect(emailYuborish).toHaveBeenCalledWith('ali@test.com', 'Salom'); // nima bilan?

jest.mock() — modulni mock qilish

Modul import qilingan bo'lsa — jest.mock() bilan almashtirish:

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

export const foydalanuvchiYarat = async ({ ism, email, parol }) => {
  const hash = await bcrypt.hash(parol, 10);
  const user = await db.users.create({ ism, email, parol: hash });
  await emailYuborish(email, 'Xush kelibsiz!');
  return user;
};
// services/userService.test.js
import { foydalanuvchiYarat } from './userService.js';

// Modullarni mock qilish
jest.mock('./emailService.js');
jest.mock('../db.js');

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

test('foydalanuvchi yaratildi va email yuborildi', async () => {
  // DB mock — ma'lumot qaytaradi
  db.users.create.mockResolvedValue({
    id: 1, ism: 'Ali', email: 'ali@test.com',
  });

  const user = await foydalanuvchiYarat({
    ism: 'Ali', email: 'ali@test.com', parol: 'Parol123',
  });

  expect(user.id).toBe(1);
  expect(emailYuborish).toHaveBeenCalledWith('ali@test.com', 'Xush kelibsiz!');
});

mockResolvedValue va mockReturnValue

// Oddiy qiymat qaytarish
fn.mockReturnValue(42);
fn(); // → 42

// Promise qaytarish (async funksiya)
fn.mockResolvedValue({ id: 1, ism: 'Ali' });
await fn(); // → { id: 1, ism: 'Ali' }

// Xato tashlash
fn.mockRejectedValue(new Error('Topilmadi'));
await fn(); // → Error tashlaydi

Amaliy misol — to'liq service testi

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

export const postYarat = async ({ sarlavha, matn, userId }) => {
  const user = await db.users.findByPk(userId);
  if (!user) throw new Error('Foydalanuvchi topilmadi');

  const post = await db.posts.create({ sarlavha, matn, userId });
  await emailYuborish(user.email, `Postingiz "${sarlavha}" e'lon qilindi`);

  return post;
};
// services/postService.test.js
import { postYarat } from './postService.js';

jest.mock('../db.js');
jest.mock('./emailService.js');

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

describe('postYarat', () => {
  test('post yaratadi va email yuboradi', async () => {
    db.users.findByPk.mockResolvedValue({ id: 1, email: 'ali@test.com' });
    db.posts.create.mockResolvedValue({ id: 10, sarlavha: 'Salom dunyo' });

    const post = await postYarat({
      sarlavha: 'Salom dunyo', matn: 'Matn...', userId: 1,
    });

    expect(post.id).toBe(10);
    expect(emailYuborish).toHaveBeenCalledWith(
      'ali@test.com',
      'Postingiz "Salom dunyo" e\'lon qilindi',
    );
  });

  test('foydalanuvchi topilmasa — xato', async () => {
    db.users.findByPk.mockResolvedValue(null); // ← foydalanuvchi yo'q

    await expect(
      postYarat({ sarlavha: 'Test', matn: '...', userId: 999 })
    ).rejects.toThrow('Foydalanuvchi topilmadi');

    expect(emailYuborish).not.toHaveBeenCalled(); // ← email yuborilmagan
  });
});

beforeEach da mock tozalash

Bir test mockni o'zgartirsa — keyingi test ham ta'sirlanaoladi:

beforeEach(() => {
  jest.clearAllMocks(); // ← har testdan oldin mockni tozalash
});

Tez-tez uchraydigan xatolar

1. Mock o'rniga real funksiya chaqirilishi

// ❌ — jest.mock import dan keyin yozilgan
import { emailYuborish } from './emailService.js';
jest.mock('./emailService.js'); // ← kech! Import allaqachon bo'ldi

// ✅ — jest.mock birinchi (Jest uni hoistlab tepaga ko'chiradi)
jest.mock('./emailService.js');
import { emailYuborish } from './emailService.js';

2. Mock qaytarmaslik

// ❌ — undefined qaytaradi
db.users.findByPk.mockResolvedValue(); // ← qiymat yo'q

// ✅
db.users.findByPk.mockResolvedValue({ id: 1, email: 'ali@test.com' });

3. Mock tozalanmasa

// ❌ — birinchi test mockni sozladi, ikkinchi ham shuni ishlatadi
test('birinchi', () => {
  db.users.findByPk.mockResolvedValue({ id: 1 });
});
test('ikkinchi', () => {
  // db.users.findByPk hali ham { id: 1 } qaytaradi!
});

// ✅
beforeEach(() => jest.clearAllMocks());

Xulosa

  • Mock — tashqi tizim (DB, email, API) o'rnidagi soxta funksiya
  • jest.fn() — oddiy mock; jest.mock('modul') — butun modulni almashtirish
  • mockResolvedValue — async mock; mockRejectedValue — xato mock
  • toHaveBeenCalled, toHaveBeenCalledWith — mock chaqirildimi tekshirish
  • beforeEach(() => jest.clearAllMocks()) — testlar orasida mock tozalash

Keyingi dars: 55-dars: Test Coverage — Istanbul bilan