> Как вы писали модульные и интеграционные тесты (JavaScript)

Уровень: senior · Роль: frontend · Язык: JavaScript · Категория: Технические вопросы

Компании: ЭНИРАН

Стек: Node.js, JavaScript

> Пример ответа

Короткий ответ

Модульные тесты писал на Jest с изоляцией зависимостей через mocking (jest.mock, sinon). Интеграционные тесты строил на Supertest + реальная БД (тестовая) или in-memory база. Для компонентов React использовал React Testing Library. Основной принцип: модульные тесты проверяют логику одного модуля, интеграционные - взаимодействие между модулями (API, БД, внешние сервисы). Покрытие старался держать выше 80%, но без фанатизма.

Подробное объяснение

Модульные тесты фокусируются на изолированной проверке функций, классов или компонентов. Для Node.js использовал Jest - он предоставляет встроенный mocking, assertions и coverage. В React-проектах применял React Testing Library, так как она поощряет тестирование поведения, а не реализации. Моки создавал для внешних зависимостей: HTTP-запросы (nock, msw), файловая система (mock-fs), таймеры (jest.useFakeTimers).

Интеграционные тесты проверяют совместную работу модулей. Для API-сервисов на Express/Koa использовал Supertest - он запускает приложение в том же процессе, но без реального порта. Базу данных поднимал в Docker-контейнере или использовал in-memory решения (SQLite, mongodb-memory-server). Для тестирования взаимодействия с внешними API применял MSW (Mock Service Worker) - он перехватывает запросы на уровне сети.

Важный принцип: модульные тесты должны быть быстрыми (миллисекунды), интеграционные - медленнее (секунды), поэтому их разделял в CI: модульные запускаются на каждый push, интеграционные - на pull request или перед деплоем.

На практике

В реальных проектах придерживался пирамиды тестирования: 70% модульных, 20% интеграционных, 10% e2e. Для модульных тестов писал тесты на каждую публичную функцию, проверял граничные случаи и ошибки. Для интеграционных - ключевые сценарии: создание ресурса через API, проверка записи в БД, обработка ошибок валидации.

Использовал TDD для критичных модулей (расчеты, парсинг данных). В legacy-коде сначала писал характеризационные тесты (snapshot-тесты для существующего поведения), затем рефакторил. Для тестовых данных применял фабрики (factory-girl, faker) - это упрощало поддержку при изменении схемы.

В CI настраивал параллельный запуск тестов (Jest --maxWorkers) и кэширование node_modules. Интеграционные тесты с БД запускал в отдельных контейнерах, чтобы избежать конфликтов.

Пример кода

JAVASCRIPT
// Модульный тест для функции расчета скидки
const { calculateDiscount } = require('./pricing');
jest.mock('./userRepository', () => ({
getUserTier: jest.fn()
}));
const { getUserTier } = require('./userRepository');
describe('calculateDiscount', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('applies 20% discount for premium users', () => {
getUserTier.mockReturnValue('premium');
expect(calculateDiscount(100)).toBe(80);
});
it('throws error for invalid price', () => {
expect(() => calculateDiscount(-10)).toThrow('Price must be positive');
});
});
// Интеграционный тест для Express API
const request = require('supertest');
const app = require('../app');
const { connectDB, disconnectDB } = require('../db');
describe('POST /api/users', () => {
beforeAll(async () => {
await connectDB(process.env.TEST_DATABASE_URL);
});
afterAll(async () => {
await disconnectDB();
});
it('creates user and returns 201', async () => {
const res = await request(app)
.post('/api/users')
.send({ name: 'Alice', email: 'alice@test.com' });
expect(res.status).toBe(201);
expect(res.body).toHaveProperty('id');
});
it('returns 400 for duplicate email', async () => {
await request(app).post('/api/users').send({ name: 'Bob', email: 'alice@test.com' });
const res = await request(app).post('/api/users').send({ name: 'Charlie', email: 'alice@test.com' });
expect(res.status).toBe(400);
expect(res.body.error).toMatch(/duplicate/i);
});
});

Как отвечать на собеседовании

Начни с четкого разделения: модульные тесты - изолированная проверка одного модуля, интеграционные - проверка связки модулей. Приведи конкретные инструменты (Jest, React Testing Library, Supertest) и объясни, почему их выбрал. Упомяни про моки и стабы, но не углубляйся в детали, если не спросят.

Покажи понимание trade-off: модульные тесты быстрые, но не ловят ошибки интеграции; интеграционные - надежнее, но медленнее и сложнее в поддержке. Расскажи про пирамиду тестирования и как распределяешь усилия.

Если спросят про покрытие - скажи, что гнался не за 100%, а за критическими сценариями. Приведи пример, когда тест спас от бага в продакшене.

Что проверяет интервьюер

Интервьюер оценивает:

  • Понимание разницы между модульными и интеграционными тестами
  • Знание инструментов и их правильное применение
  • Умение изолировать зависимости (mocking, stubbing)
  • Опыт работы с тестовыми окружениями (БД, внешние API)
  • Понимание пирамиды тестирования и стратегии покрытия
  • Способность писать читаемые и поддерживаемые тесты

Обрати внимание: если кандидат путает модульные и интеграционные тесты или не может объяснить, зачем нужны моки - это красный флаг.

Типичные ошибки

  • Тестирование реализации вместо поведения (например, проверка вызова setState вместо проверки рендера)
  • Использование реальных HTTP-запросов в модульных тестах (делает тесты медленными и нестабильными)
  • Отсутствие очистки тестовых данных (тесты начинают влиять друг на друга)
  • Слишком глубокие моки (mock всей БД вместо одного репозитория)
  • Игнорирование асинхронных ошибок (не ждать промисы, не использовать done)
  • Написание тестов только для happy path без проверки ошибок и граничных случаев

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью