Jest моки и стабы
Mock и stub в Jest — инструменты замены реальных зависимостей (функций, модулей, HTTP-запросов) контролируемыми тестовыми дублёрами для изоляции тестируемого кода.
Зачем нужно
Без мокирования unit-тест превращается в integration-тест: он стучится в БД, делает HTTP-запросы, пишет файлы — становится медленным и нестабильным. Mock позволяет проверить, как код реагирует на конкретный ответ зависимости (успех, ошибку, пустой результат), без запуска реальной инфраструктуры. Spy фиксирует, с какими аргументами вызвалась функция.
Где используется
- Замена HTTP-клиента (axios, fetch) в unit-тестах сервисов
- Замена модулей файловой системы (
fs), БД-клиентов, email-сервисов - Проверка, что колбэк был вызван с нужными аргументами
- Тестирование обработки ошибок — mock бросает исключение, тест проверяет поведение
Основной контент
jest.fn — функция-mock
test('вызывает колбэк с правильным аргументом', () => {
const callback = jest.fn;
[1, 2, 3].forEach(callback);
expect(callback).toHaveBeenCalledTimes(3);
expect(callback).toHaveBeenCalledWith(1, 0, [1, 2, 3]); // (value, index, array)
expect(callback).toHaveBeenLastCalledWith(3, 2, [1, 2, 3]);
});
Настройка возвращаемого значения
const mockFn = jest.fn;
// Вернуть значение один раз
mockFn.mockReturnValueOnce(42);
mockFn.mockReturnValueOnce(99);
mockFn; // → 42
mockFn; // → 99
mockFn; // → undefined
// Всегда возвращать одно значение
mockFn.mockReturnValue('default');
// Промис
mockFn.mockResolvedValue({ id: 1, name: 'Alice' });
mockFn.mockRejectedValue(new Error('Not found'));
Мокирование модуля целиком
// emailService.js
const nodemailer = require('nodemailer');
async function sendWelcome(email) {
const transport = nodemailer.createTransport({ /* ... */ });
await transport.sendMail({ to: email, subject: 'Welcome' });
}
module.exports = { sendWelcome };
// emailService.test.js
jest.mock('nodemailer');
const nodemailer = require('nodemailer');
const { sendWelcome } = require('./emailService');
test('отправляет welcome-письмо', async () => {
const mockSendMail = jest.fn.mockResolvedValue({ messageId: '123' });
nodemailer.createTransport.mockReturnValue({ sendMail: mockSendMail });
await sendWelcome('user@example.com');
expect(mockSendMail).toHaveBeenCalledWith(
expect.objectContaining({ to: 'user@example.com' })
);
});
jest.spyOn — шпион на существующий метод
const fs = require('fs');
test('логирует в файл', () => {
const spy = jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {});
writeLog('error', 'Something went wrong');
expect(spy).toHaveBeenCalledWith(
expect.stringContaining('app.log'),
expect.stringContaining('Something went wrong')
);
spy.mockRestore(); // восстановить оригинальный метод
});
Мокирование fetch / axios
// Мок для fetch (глобальный)
global.fetch = jest.fn.mockResolvedValue({
ok: true,
json: jest.fn.mockResolvedValue({ id: 1, name: 'Alice' }),
});
test('загружает пользователя', async () => {
const user = await getUser(1);
expect(user.name).toBe('Alice');
expect(fetch).toHaveBeenCalledWith('https://api.example.com/users/1');
});
// Мок для axios
jest.mock('axios');
const axios = require('axios');
test('обрабатывает ошибку сети', async () => {
axios.get.mockRejectedValue(new Error('Network Error'));
await expect(fetchData).rejects.toThrow('Network Error');
});
Очистка между тестами
afterEach(() => {
jest.clearAllMocks; // сбросить счётчики вызовов и аргументы
// jest.resetAllMocks; // + убрать mockReturnValue
// jest.restoreAllMocks; // + восстановить спаи (spyOn)
});
Частые ошибки
- Мокировать после
require—jest.mockвызывается до импорта модуля (Jest поднимает его в начало); порядок в файле не важен, но важно не забыть вызов - Не восстанавливать
spyOn— безspy.mockRestore()илиjest.restoreAllMocksоригинальный метод остаётся подменённым в следующих тестах mockReturnValuevsmockResolvedValue— для async-функций нуженmockResolvedValue, иначе вернётся не промис, а объект{then: ...}- Проверять факт вызова вместо результата — если можно проверить результат функции — проверяй результат; mock-проверка «был ли вызван» — запасной вариант
- Слишком широкие моки —
jest.mock('./db')мокирует весь модуль; если нужна только одна функция — используйjest.spyOn