> Как вы писали модульные и интеграционные тесты (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 APIconst 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 без проверки ошибок и граничных случаев
> Похожие задачи по JavaScript
Какие способы оптимизации time to first byte существуют
Как использовать асинхронные функции при переборе элементов, например в reduce
Что такое event loop в JavaScript и как он работает с микрозадачами и макрозадачами
Нужно ли добавлять Promise к выходному типу асинхронной функции в TypeScript
> Похожие задачи по frontend
Какие способы оптимизации time to first byte существуют
Как использовать асинхронные функции при переборе элементов, например в reduce
Что такое event loop в JavaScript и как он работает с микрозадачами и макрозадачами
Как определить тип переменной через ReturnType в TypeScript
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью