TL;DR

  • Mocha предоставляет структуру тестов (describe, it) — приноси свою assertion library
  • Сочетай с Chai для читаемых assertions: expect(value).to.equal(expected)
  • Hooks: before, after, beforeEach, afterEach для setup/teardown
  • Async поддержка: callbacks (done), promises, async/await — всё работает
  • Гибкий и модульный — выбирай свои инструменты

Идеально для: Node.js проектов, команд, желающих выбор assertion library Пропусти, если: Предпочитаешь всё-в-одном решения (используй Jest) Время чтения: 14 минут

Твоему Node.js проекту нужны тесты. Jest кажется тяжёлым для маленького сервиса. Ты хочешь выбрать свой стиль assertions. Тебе нужно что-то, что работает с существующими инструментами.

Mocha не мешает. Он запускает тесты, предоставляет hooks, обрабатывает async — не более. Ты выбираешь assertion library, инструмент мокирования и reporter.

Что такое Mocha?

Mocha — JavaScript тест-фреймворк для Node.js и браузеров. Предоставляет структуру для организации тестов, но намеренно оставляет assertions и mocking другим библиотекам.

Почему Mocha:

  • Гибкий — работает с любой assertion library
  • Async-friendly — callbacks, promises, async/await
  • Богатая экосистема — много reporters и плагинов
  • Browser support — те же тесты работают в Node и браузере

Установка и настройка

npm install mocha chai --save-dev
// package.json
{
    "scripts": {
        "test": "mocha",
        "test:watch": "mocha --watch"
    }
}
// .mocharc.json
{
    "spec": "test/**/*.test.js",
    "timeout": 5000,
    "recursive": true
}

Написание первого теста

// test/calculator.test.js
const { expect } = require('chai');
const Calculator = require('../src/calculator');

describe('Calculator', () => {
    describe('add()', () => {
        it('должен сложить два положительных числа', () => {
            const calc = new Calculator();
            expect(calc.add(2, 3)).to.equal(5);
        });

        it('должен обработать отрицательные числа', () => {
            const calc = new Calculator();
            expect(calc.add(-1, 1)).to.equal(0);
        });
    });
});

Chai Assertions

const { expect } = require('chai');

// Равенство
expect(value).to.equal(5);
expect(value).to.deep.equal({ a: 1 });

// Проверка типов
expect('hello').to.be.a('string');
expect([1, 2]).to.be.an('array');

// Сравнения
expect(10).to.be.above(5);
expect(10).to.be.below(20);

// Строки
expect('hello world').to.include('world');
expect('hello').to.have.lengthOf(5);

// Массивы
expect([1, 2, 3]).to.include(2);
expect([1, 2, 3]).to.have.lengthOf(3);

// Объекты
expect({ a: 1 }).to.have.property('a');

// Ошибки
expect(() => fn()).to.throw();
expect(() => fn()).to.throw('error message');

Lifecycle Hooks

describe('User Service', () => {
    let db;

    before(async () => {
        // Один раз перед всеми тестами
        db = await connectToDatabase();
    });

    after(async () => {
        // Один раз после всех тестов
        await db.close();
    });

    beforeEach(() => {
        // Перед каждым тестом
    });

    afterEach(async () => {
        // После каждого теста
        await db.collection('users').deleteMany({});
    });

    it('должен создать пользователя', async () => {
        // ...
    });
});

Async тестирование

Async/Await (Рекомендуется)

it('должен получить пользователя', async () => {
    const user = await fetchUser(1);
    expect(user.name).to.equal('John');
});

it('должен обработать ошибки', async () => {
    try {
        await fetchUser(999);
        expect.fail('Должен был выбросить ошибку');
    } catch (err) {
        expect(err.message).to.include('not found');
    }
});

Timeout

it('должен завершиться за 5 секунд', async function() {
    this.timeout(5000);
    await longRunningOperation();
});

Mocking с Sinon

npm install sinon --save-dev
const sinon = require('sinon');
const axios = require('axios');

describe('API Client', () => {
    let axiosStub;

    beforeEach(() => {
        axiosStub = sinon.stub(axios, 'get');
    });

    afterEach(() => {
        axiosStub.restore();
    });

    it('должен получить данные', async () => {
        axiosStub.resolves({ data: { id: 1, name: 'Test' } });

        const result = await apiClient.fetchUser(1);

        expect(result.name).to.equal('Test');
    });
});

CI/CD интеграция

# .github/workflows/test.yml
name: Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npm test

Mocha с помощью ИИ

Что ИИ делает хорошо:

  • Генерировать тест-кейсы из сигнатур функций
  • Создавать вариации assertions для edge cases
  • Конвертировать между стилями assertions
  • Предлагать mock-реализации

Что требует людей:

  • Решать что тестировать
  • Понимать бизнес-логику
  • Отлаживать flaky тесты

FAQ

Что такое Mocha?

Mocha — гибкий JavaScript тест-фреймворк для Node.js и браузеров. Предоставляет структуру тестов (describe, it), lifecycle hooks (before, after) и async поддержку, но намеренно не включает assertions или mocking. Ты сочетаешь его с Chai для assertions и Sinon для mocking.

Mocha vs Jest — что лучше?

Jest — всё-в-одном со встроенными assertions, mocking и coverage. Mocha модулярный — ты выбираешь каждый компонент. Jest проще для старта; Mocha предлагает больше гибкости. Используй Jest для React проектов, Mocha когда нужны специфичные стили assertions.

Какая assertion library работает с Mocha?

Chai — самый популярный выбор с тремя стилями: expect (BDD), should (BDD) и assert (TDD). Встроенный Node assert тоже работает. Большинство команд предпочитают Chai expect стиль за читаемость.

Может ли Mocha тестировать async код?

Да, отлично. Mocha поддерживает три async паттерна: callbacks (параметр done), promises (вернуть promise) и async/await (просто использовать async функцию). Mocha обрабатывает все паттерны нативно.

Официальные ресурсы

Смотрите также