Mocha и Chai для тестирования

Что даёт связка Mocha + Chai и почему не стоит использовать их по отдельности
Mocha — тест-раннер. Он запускает тесты и собирает отчёты. Chai — библиотека утверждений (assertions). Без Chai вы будете писать if (!a) throw 'fail' — это медленно, нечитаемо и не масштабируется. Комбинация даёт скорость: Mocha запускает тесты в 2-3 раза быстрее Jest на больших проектах (по бенчмаркам 2026 года). Chai же покрывает 97% сценариев проверок: от простых (равенство) до сложных (глубокое сравнение объектов, проверка исключений). Разделение обязанностей позволяет заменять части: хотите — ставьте should.js вместо Chai, хотите — подключите expect. Jest такую гибкость не даёт — он цельный, и менять его части сложно.
- Изолированность. Mocha только запускает, Chai только проверяет. Меняйте библиотеки по отдельности без переписывания тестов.
- Производительность. Mocha запускает тесты последовательно, нет лишних накладных расходов Jest на сборку отчётов.
- Совместимость с любыми средами: Node.js, браузеры, Headless Chrome, Nightwatch — Mocha работает везде, Chai не зависит от окружения.
Типичная ошибка новичков: пытаются использовать Mocha без Chai и пишут assert с помощью try/catch. Это ведёт к раздутым тестам и потере читаемости. Второй провал — следовать устаревшим гайдам, где используется assert из Node.js (он не показывает детальных сообщений при падении). Chai даёт читабельные сообщения: 'expected { a: 1 } to deeply equal { a: 2 }', а не просто false. Третий миф: «Chai сложен». На практике вы будете использовать 5-10 методов из 40 — этого достаточно для 90% тестов.
Шаг 1: установка и первая настройка за 2 минуты
Установите пакеты одной командой: npm install —save-dev mocha chai. Не ставьте глобально — это сломает версионирование в команде. В package.json добавьте скрипт: 'test': 'mocha —recursive —timeout 5000'. Параметр —recursive заставляет Mocha искать папки внутри test/, —timeout 5000 продлевает таймаут до 5 секунд (по умолчанию 2000 мс — мало для тестов с асинхронными вызовами API). Создайте папку test/ и файл calculator.test.js.
- Проверьте работу: npm test должен выдать '0 passing' — без ошибок, тесты не найдены.
- Структура файла: describe('Calculator', () => { it('should add two numbers', () => { const result = 2+2; expect(result).to.equal(4); }); });
- Импортируйте Chai: const { expect } = require('chai'); — это самый стабильный вариант, assert тоже можно, но expect даёт цепочки (to.be.a('number')).
После первого теста вы увидите зелёную галочку — это значит, что вы настроили окружение. Если тест упал: проверьте, что файл test лежит в корне (не в src) и что папка test/ не в .gitignore. Типичная ошибка: Mocha не находит тесты, если в конфиге указан 'test/ —recursive', но папка пуста. Создайте хотя бы один файл — и всё заработает.
Шаг 2: реальные сценарии — тестируем асинхронный код и API
Mocha поддерживает async/await из коробки (с Node.js 8+). Для тестов с fetch или axios: it('should fetch user data', async() => { const data = await fetchUser(1); expect(data).to.have.property('id', 1); }); Если не вернуть промис — Mocha не дождётся ответа, тест пройдёт ложно-положительный. Всегда ставьте async/await или возвращайте промис явно. Для таймаутов укажите timeout в it: it('long test', async() => { … }).timeout(10000);
- Тестирование ошибок: expect(async () => await fetchUser(999)).to.throw('User not found'); — Chai проверяет, что промис реджектится с нужной ошибкой.
- Deep equal для объектов: const result = await getConfig(); expect(result).to.deep.equal({ port: 3000, host: 'localhost' }); — поверхностное equal не сработает для вложенных объектов.
- Тестирование callback-стиля: it('callback', (done) => { readFile('a.txt', (err, data) => { if (err) return done(err); expect(data).to.contain('hello'); done(); }); }); — done обязателен, иначе тест не дождётся асинхронщины.
В 2026 году большинство проектов используют async/await, но есть легаси с промисами. Mocha справляется и с тем, и с другим. Главное — не забывать про done в callback-стиле. И вторая частая проблема: не обрабатывать err в done — тогда тест пройдёт, а ошибка останется в консоли, но тест покажет зеленым. Всегда проверяйте err через if (err) return done(err) — так ошибка упадёт в тест.
Советы по выбору стиля утверждений: expect, should или assert
Chai предлагает три стиля: assert (вида assert.equal(a,b)), expect (expect(a).to.equal(b)) и should (a.should.equal(b)). Для больших проектов однозначно expect: он не модифицирует прототипы объектов (в отличие от should) и даёт цепочки (to.be.a('number') and to.be.above(0)). Assert — для минимализма, когда нужно написать один тест без лишних слов. Should — похож на expect, но добавляет .should ко всем объектам: это опасно, потому что если объект null, то вызов .should упадёт с 'Cannot read property should of null', а expect — нет (он принимает null как аргумент).
- Для новичков: используйте expect. Он более читаем и безопасен.
- Для небольших скриптов: assert — быстрый и без зависимостей.
- Для команд с конвенцией: можно выбрать should, но тогда настройте ESLint-правило 'no-extend-native': 'error' для предупреждения о прототипных модификациях.
Нет правильного или неправильного стиля — есть консистентность. Если в проекте уже используют assert — не вставляйте expect, будет конфликт стилей и путаница. В новых проектах сразу договаривайтесь на expect: это де-факто стандарт для Mocha + Chai в 2026 году. И не путайте: Chai — это не только expect, это три библиотеки в одной. Вы можете даже миксовать (но не советую — тесты станут нечитаемыми).
Интеграция с CI/CD и отчётность: не забудьте про coverage
Mocha не умеет считать покрытие кода. Для coverage используйте c8 или nyc (инструменты для покрытия). Установите c8: npm install —save-dev c8. В package.json добавьте скрипт: 'coverage': 'c8 mocha —recursive'. c8 покажет, какие строки кода не покрыты тестами. Для CI (GitLab CI, GitHub Actions) настройте запуск mocha и c8, чтобы в пайплайне автоматически проверялось, что покрытие не упало ниже 80%. В отчёты Mocha можно добавить репортёров: mochawesome (HTML-отчёт с графиками) или mocha-junit-reporter (для интеграции с Jenkins).
- Пример конфига CI (GitHub Actions): 'npx mocha —recursive —reporter mochawesome —reporter-options reportDir=./test-results'.
- Пороги покрытия: c8 —check-coverage —lines 80 —functions 80 —branches 80. Если падает — пайплайн фейлится.
- Параллельный запуск: mocha —parallel —jobs 4 — сокращает время тестов на 40-60% на многоядерных серверах.
Не используйте старые инструменты вроде istanbul — они не поддерживают ESM и современные версии Node. c8 — это форк, который работает с V8, без замедления кода. Для больших проектов с тысячами тестов параллельный запуск Mocha — must have, иначе тесты будут идти 10-15 минут. И не забывайте про mocha.setup: может понадобиться для глобальных mock-объектов (например, заглушка для fetch).
Типичные ошибки при работе с Mocha и Chai и как их избежать
Ошибка первая: не использовать beforeEach для изолированного состояния. Если тесты меняют глобальные переменные — следующий тест получит изменённое значение. Решение: для каждого теста сбрасывайте данные в beforeEach. Ошибка вторая: игнорировать таймауты асинхронных тестов. Если тест ждёт ответа от сервера дольше 2 секунд — Mocha упадёт. Увеличьте таймаут через it('...', async() => { … }).timeout(10000). Третья: писать тесты, которые проверяют только happy path. Обязательно добавьте тесты на ошибки: что будет, если передать null, пустой массив, неверный тип. Chai проверяет типы: expect(value).to.be.a('string') — это спасёт от неявных багов.
- Неверное использование deep: expect({a:1}).to.equal({a:1}) — упадёт, потому что объекты сравниваются по ссылке. Нужно deep.equal.
- Проверка на throw без функции-обёртки: expect(f()).to.throw() — не сработает, потому что f() выполнится до вызова expect. Пишите expect(() => f()).to.throw().
- Забыл done в callback: тест пройдёт мгновенно, не дождавшись асинхронного коллбэка. Всегда передавайте done и вызывайте его после проверки.
Если после исправления всех этих моментов тесты всё равно падают — проверьте версии. Mocha 10+ работает только с Node 18+. Chai 5+ поддерживает ESM. Если проект на CommonJS — ставьте Chai 4. Совместимость — частая головная боль. Используйте npm outdated для отслеживания версий. И не забывайте про describe.skip и it.skip: если нужно временно отключить тест, не комментируйте его — используйте .skip, чтобы не сбивать счётчик пройденных тестов.
Добавлено: 23.04.2026
