8. Mocking

Зачем нужны моки?
Заголовок раздела «Зачем нужны моки?»Мок заменяет реальную зависимость на контролируемую имитацию:
- Изолировать тестируемую функцию
- Контролировать поведение зависимостей
- Проверить, что функция вызывалась правильно
- Ускорить тесты (нет HTTP, нет БД)
jest.fn() / vi.fn() — Mock Function
Заголовок раздела «jest.fn() / vi.fn() — Mock Function»import { jest } from '@jest/globals'; // или import { vi } from 'vitest'
// Создать мок-функциюconst mockFn = jest.fn();
// Настроить возвращаемое значениеmockFn.mockReturnValue(42);mockFn.mockReturnValueOnce(1).mockReturnValueOnce(2); // разные значения
// Асинхронные возвращаемые значенияconst mockAsync = jest.fn();mockAsync.mockResolvedValue({ name: 'Alice' }); // Promise.resolvemockAsync.mockRejectedValue(new Error('Failed')); // Promise.reject
// Вызов и проверкиmockFn(1, 2);mockFn('hello');
expect(mockFn).toHaveBeenCalled(); // был вызванexpect(mockFn).toHaveBeenCalledTimes(2); // 2 разаexpect(mockFn).toHaveBeenCalledWith(1, 2); // с этими аргументамиexpect(mockFn).toHaveBeenLastCalledWith('hello');// последний вызов
// Очистить историю вызововmockFn.mockClear(); // очищает вызовы, но сохраняет имплементациюmockFn.mockReset(); // очищает вызовы И имплементациюmockFn.mockRestore(); // только для spyOn — восстанавливает оригиналМокирование модуля (jest.mock / vi.mock)
Заголовок раздела «Мокирование модуля (jest.mock / vi.mock)»// ── Автоматический мок ──jest.mock('./emailService');// emailService.sendEmail будет jest.fn() автоматически
// ── Ручной мок ──jest.mock('./database', () => ({ findUser: jest.fn(), createUser: jest.fn(), deleteUser: jest.fn(),}));
// ── Пример: тест без реальной БД ──import { findUser } from './database';import { getUserProfile } from './userService';
jest.mock('./database', () => ({ findUser: jest.fn(),}));
test('getUserProfile returns formatted profile', async () => { findUser.mockResolvedValue({ id: 1, name: 'Alice', created_at: new Date('2024-01-01'), });
const profile = await getUserProfile(1);
expect(findUser).toHaveBeenCalledWith(1); expect(profile).toMatchObject({ displayName: 'Alice', memberSince: '2024', });});Мокирование ES модулей (Vitest)
Заголовок раздела «Мокирование ES модулей (Vitest)»export async function fetchUser(id: number) { const res = await fetch(`/api/users/${id}`); return res.json();}
// userService.tsimport { fetchUser } from './api';
export async function getDisplayName(id: number) { const user = await fetchUser(id); return `${user.firstName} ${user.lastName}`;}
// userService.test.tsimport { vi, describe, it, expect } from 'vitest';import { getDisplayName } from './userService';
vi.mock('./api', () => ({ fetchUser: vi.fn(),}));
import { fetchUser } from './api';
describe('getDisplayName', () => { it('returns full name', async () => { vi.mocked(fetchUser).mockResolvedValue({ firstName: 'Alice', lastName: 'Smith', });
const name = await getDisplayName(1); expect(name).toBe('Alice Smith'); });});Мокирование Node.js модулей
Заголовок раздела «Мокирование Node.js модулей»// Мок файловой системыjest.mock('fs/promises', () => ({ readFile: jest.fn().mockResolvedValue('file content'), writeFile: jest.fn().mockResolvedValue(undefined),}));
// Мок pathjest.mock('path', () => ({ ...jest.requireActual('path'), // сохранить оригинальные методы join: jest.fn().mockReturnValue('/mocked/path'),}));Мокирование fetch
Заголовок раздела «Мокирование fetch»// Глобальный мок fetchglobal.fetch = jest.fn();
beforeEach(() => { global.fetch.mockClear();});
test('fetches and processes data', async () => { global.fetch.mockResolvedValueOnce({ ok: true, json: () => Promise.resolve({ id: 1, name: 'Alice' }), });
const result = await getUserData(1);
expect(fetch).toHaveBeenCalledWith('/api/users/1'); expect(result.name).toBe('Alice');});
test('handles fetch error', async () => { global.fetch.mockResolvedValueOnce({ ok: false, status: 404 });
await expect(getUserData(999)).rejects.toThrow('User not found');});Практические задания
Заголовок раздела «Практические задания»- Замокируй
emailService.send()и проверь, что он вызывается при регистрации - Замокируй
fetchи протестируй обработку ошибок API - Используй
mockReturnValueOnceдля последовательных разных ответов
- jest.fn()/vi.fn() — мок-функция с историей вызовов
- jest.mock()/vi.mock() — мокирование целого модуля
- mockResolvedValue / mockRejectedValue — для async функций
- mockClear() — между тестами, mockRestore() — для spyOn