Стратегия моков в тестах
Что мокать, что нет, и как не сделать тесты бесполезными от перемокания.
Спроектируй стратегию моков.
Главный принцип
Мокай зависимости которые дороги, медленны или непредсказуемы. Не мокай зависимости которые тестируешь.
Что МОКАТЬ всегда
- Внешние API (Stripe, OpenAI, Twilio...)
- Время (
Date.now(), таймеры) - Случайность (
Math.random(), UUID) - File system (зависит от уровня теста)
- Сеть в unit-тестах
Что НЕ МОКАТЬ
- Свои собственные функции (это уже не unit-тест функции, это тест моков)
- Pure-логику
- БД в integration-тестах (используй test-DB или транзакции с откатом)
Способы
A. Manual mock
const sendEmail = jest.fn().mockResolvedValue({ id: 'msg-1' });
// тест
await registerUser({ email: '...', sendEmail });
expect(sendEmail).toHaveBeenCalledWith({ to: '...', template: 'welcome' });
B. Mock module целиком
jest.mock('@/lib/email', () => ({
sendEmail: jest.fn().mockResolvedValue({ id: 'msg-1' })
}));
C. MSW (Mock Service Worker) для HTTP
Перехватывает реальные fetch запросы на уровне сети. Тесты максимально близки к продакшен-коду.
server.use(
http.post('https://api.stripe.com/v1/charges', () => {
return HttpResponse.json({ id: 'ch_test', amount: 1000 });
})
);
D. Test doubles по уровням
| Тип | Что это | Когда |
|---|---|---|
| Dummy | Заглушка, никогда не используется | Заполнить параметр |
| Stub | Возвращает фиксированное значение | Простой кейс |
| Fake | Работающая упрощённая реализация (in-memory DB) | Integration |
| Spy | Stub + запоминает вызовы | Проверить что вызвали |
| Mock | Stub + проверки в конце | Полный контракт |
Anti-patterns
✗ Мокать внутренние функции того же модуля
// плохо — теперь не проверяешь логику
jest.spyOn(myModule, 'helper').mockReturnValue(...);
✗ Перемокание — все зависимости моки, тестируется только сам мок
const user = { id: 1, name: 'Test', active: true };
expect(formatUser(user)).toBe(...); // что тут проверяется?
✗ Mock data копирует prod — изменилась схема → 100 тестов сломались
✗ Глобальное состояние моков — один тест меняет, другой ломается
Хорошие практики
✓ Fakes для сложных зависимостей
class InMemoryUserRepo implements UserRepo {
private users = new Map();
async save(u) { this.users.set(u.id, u); }
async find(id) { return this.users.get(id); }
}
✓ Factories для тестовых данных
const buildUser = (overrides = {}) => ({
id: 'u-1', email: 'test@test.com', active: true, ...overrides
});
✓ Reset моков между тестами
beforeEach(() => jest.clearAllMocks());
Тест: "тесты ловят настоящие баги?"
Сломай намеренно одну строчку в проде-коде. Если тесты прошли — они моки тестируют, не код. Удали или переделай.
Принципы
- Чем меньше моков — тем ближе к реальности
- Fake (in-memory) часто лучше mock
- MSW > monkey-patching fetch
- Если приходится мокать 5 модулей чтобы протестировать один — этот один делает слишком много
Стратегия внутренней перелинковки
Граф ссылок: какие страницы — хабы, какие — спицы, как передавать link equity.
North-Star метрика и input-метрики
Одна главная метрика которая отражает успех продукта + 3-5 драйверов под неё.
Позиционирование продукта
Одна формула: для [кого] · которые [боль] · продукт — это [категория] · который [ценность] · в отличие от [альтернатив].