Mocha для JavaScript (Node.js): полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Mocha для JavaScript (Node.js): полное руководство с примерами

1. Что это и зачем нужна

Mocha — это мощный, гибкий и широко используемый тестовый фреймворк для JavaScript, работающий в среде Node.js и в браузере. Он был создан в 2011 году разработчиком TJ Holowaychuk и с тех пор стал одним из стандартов де-факто для тестирования JavaScript-приложений. Mocha предоставляет разработчикам чистый, модульный и выразительный API для написания как простых модульных тестов, так и сложных интеграционных и асинхронных тестовых сценариев.

Основная цель Mocha — сделать процесс тестирования максимально простым и интуитивно понятным. В отличие от некоторых других фреймворков, Mocha не навязывает жёсткую структуру тестов и не включает в себя встроенную библиотеку утверждений (assertions). Вместо этого она предоставляет каркас (framework) для организации тестов (describe/it/hooks) и запуска их с различными отчётами (reporters), оставляя выбор библиотеки для проверки утверждений на усмотрение разработчика. Это делает Mocha чрезвычайно гибкой: вы можете использовать её с встроенным модулем assert, популярной библиотекой Chai (которая предоставляет стили BDD/TDD), Should.js или любой другой.

Зачем нужна Mocha? В современной разработке программного обеспечения тестирование — это не роскошь, а необходимость. Mocha позволяет автоматизировать проверку корректности работы кода, выявлять регрессии после изменений, документировать поведение функций и модулей, а также повышать общую надёжность и стабильность приложения. Благодаря поддержке асинхронного тестирования (колбэки, промисы, async/await) и возможности работы с различными библиотеками утверждений, Mocha идеально подходит для тестирования серверных приложений на Node.js, API, баз данных и любых других компонентов.

2. Установка

Mocha устанавливается через менеджер пакетов npm (Node Package Manager). Рекомендуется устанавливать её как зависимость для разработки (devDependency), так как тесты не нужны в production-окружении.

Точная команда для установки:

npm install --save-dev mocha

После установки вы можете запускать тесты, добавив скрипт в файл package.json:

{
  "scripts": {
    "test": "mocha"
  }
}

Теперь тесты можно запускать командой:

npm test

Для глобальной установки (не рекомендуется, но возможно):

npm install -g mocha

3. Быстрый старт — минимальный рабочий пример

Создайте файл test.js со следующим содержимым:

const assert = require('assert');

// Функция, которую будем тестировать
function add(a, b) {
  return a + b;
}

// Тестовый набор (suite)
describe('Функция add', function() {
  // Конкретный тест (spec)
  it('должна возвращать сумму двух чисел', function() {
    // Утверждение (assertion)
    assert.strictEqual(add(2, 3), 5);
  });

  it('должна работать с отрицательными числами', function() {
    assert.strictEqual(add(-1, -1), -2);
  });
});

Запустите тест:

npx mocha test.js

Вы должны увидеть зелёный отчёт о прохождении двух тестов.

4. Основные методы/функции/классы

Mocha предоставляет глобальные функции, которые не нужно импортировать (если они не отключены опцией --require). Ниже приведены 8 ключевых методов с сигнатурами, описанием и примерами.

4.1 describe(name, fn)

Сигнатура: describe(name: string, fn: function): void

Что делает: Создаёт тестовый набор (suite) — группу связанных тестов. Используется для логической организации тестов. Может быть вложенным.

describe('Калькулятор', function() {
  describe('Сложение', function() {
    it('должно складывать два числа', function() {
      // тест
    });
  });
});

4.2 it(name, fn)

Сигнатура: it(name: string, fn: function): void

Что делает: Определяет отдельный тест (spec). Внутри функции fn размещаются утверждения и проверки.

it('должно возвращать true для пустого массива', function() {
  const result = isEmpty([]);
  assert.strictEqual(result, true);
});

4.3 before(fn)

Сигнатура: before(fn: function): void

Что делает: Выполняет функцию fn один раз перед запуском всех тестов в текущем наборе describe. Используется для настройки общего состояния (например, подключение к БД).

describe('База данных', function() {
  let db;

  before(function(done) {
    db = new Database();
    db.connect(done); // асинхронная инициализация
  });

  it('должна быть доступна', function() {
    assert.ok(db.isConnected());
  });
});

4.4 after(fn)

Сигнатура: after(fn: function): void

Что делает: Выполняет функцию fn один раз после завершения всех тестов в наборе. Используется для очистки ресурсов (закрытие соединений, удаление временных файлов).

after(function() {
  db.close();
  console.log('Соединение с БД закрыто');
});

4.5 beforeEach(fn)

Сигнатура: beforeEach(fn: function): void

Что делает: Выполняет функцию fn перед каждым тестом it в текущем наборе. Идеально подходит для сброса состояния или подготовки изолированных данных для каждого теста.

describe('Пользователь', function() {
  let user;

  beforeEach(function() {
    user = new User({ name: 'Alice' });
  });

  it('должен иметь имя', function() {
    assert.strictEqual(user.name, 'Alice');
  });

  it('должен быть активным по умолчанию', function() {
    assert.strictEqual(user.isActive, true);
  });
});

4.6 afterEach(fn)

Сигнатура: afterEach(fn: function): void

Что делает: Выполняет функцию fn после каждого теста. Полезно для очистки данных, созданных в beforeEach.

afterEach(function() {
  user = null; // очищаем ссылку
});

4.7 it.skip(name, fn)

Сигнатура: it.skip(name: string, fn: function): void

Что делает: Пропускает (игнорирует) конкретный тест. Тест не будет выполняться, но будет отмечен в отчёте как пропущенный (pending). Полезно для временного отключения сломанных или ещё не реализованных тестов.

it.skip('сложный тест, который пока не готов', function() {
  // Этот код не выполнится
  assert.fail('Not implemented');
});

4.8 describe.skip(name, fn)

Сигнатура: describe.skip(name: string, fn: function): void

Что делает: Пропускает целый набор тестов. Все вложенные тесты и хуки (before, after и т.д.) игнорируются.

describe.skip('Устаревший модуль', function() {
  it('тест 1', function() { /* ... */ });
  it('тест 2', function() { /* ... */ });
});

5. Реальный практический пример (100-200 строк кода)

Рассмотрим пример тестирования простого REST API сервера на базе Express. Мы создадим модуль для управления списком задач (Todo) и напишем для него тесты, используя Mocha и библиотеку утверждений Chai.

Файл: src/todo.js (реализация)

class TodoList {
  constructor() {
    this.tasks = [];
    this.nextId = 1;
  }

  addTask(title) {
    if (!title || typeof title !== 'string') {
      throw new Error('Title is required and must be a string');
    }
    const task = { id: this.nextId++, title, completed: false };
    this.tasks.push(task);
    return task;
  }

  getTask(id) {
    const task = this.tasks.find(t => t.id === id);
    if (!task) {
      throw new Error(`Task with id ${id} not found`);
    }
    return task;
  }

  completeTask(id) {
    const task = this.getTask(id);
    task.completed = true;
    return task;
  }

  deleteTask(id) {
    const index = this.tasks.findIndex(t => t.id === id);
    if (index === -1) {
      throw new Error(`Task with id ${id} not found`);
    }
    this.tasks.splice(index, 1);
    return true;
  }

  getAllTasks() {
    return this.tasks;
  }
}

module.exports = TodoList;

Файл: test/todo.test.js (тесты)

const chai = require('chai');
const expect = chai.expect;
const TodoList = require('../src/todo');

describe('TodoList', function() {
  let todoList;

  // Хук: создаём новый экземпляр перед каждым тестом
  beforeEach(function() {
    todoList = new TodoList();
  });

  describe('addTask()', function() {
    it('должна добавлять задачу и возвращать её с id и completed=false', function() {
      const task = todoList.addTask('Купить молоко');
      expect(task).to.have.property('id', 1);
      expect(task).to.have.property('title', 'Купить молоко');
      expect(task).to.have.property('completed', false);
    });

    it('должна увеличивать id для каждой новой задачи', function() {
      todoList.addTask('Задача 1');
      const task2 = todoList.addTask('Задача 2');
      expect(task2.id).to.equal(2);
    });

    it('должна выбрасывать ошибку при пустом заголовке', function() {
      expect(() => todoList.addTask('')).to.throw('Title is required and must be a string');
    });

    it('должна выбрасывать ошибку при заголовке не строке', function() {
      expect(() => todoList.addTask(123)).to.throw('Title is required and must be a string');
    });
  });

  describe('getTask()', function() {
    it('должна возвращать задачу по существующему id', function() {
      todoList.addTask('Тестовая задача');
      const task = todoList.getTask(1);
      expect(task.title).to.equal('Тестовая задача');
    });

    it('должна выбрасывать ошибку для несуществующего id', function() {
      expect(() => todoList.getTask(999)).to.throw('Task with id 999 not found');
    });
  });

  describe('completeTask()', function() {
    it('должна отмечать задачу как выполненную', function() {
      todoList.addTask('Сделать домашку');
      const completedTask = todoList.completeTask(1);
      expect(completedTask.completed).to.be.true;
    });

    it('должна выбрасывать ошибку для несуществующей задачи', function() {
      expect(() => todoList.completeTask(42)).to.throw('Task with id 42 not found');
    });
  });

  describe('deleteTask()', function() {
    it('должна удалять задачу и возвращать true', function() {
      todoList.addTask('Временная задача');
      const result = todoList.deleteTask(1);
      expect(result).to.be.true;
      expect(todoList.getAllTasks()).to.have.lengthOf(0);
    });

    it('должна выбрасывать ошибку при удалении несуществующей задачи', function() {
      expect(() => todoList.deleteTask(100)).to.throw('Task with id 100 not found');
    });
  });

  describe('getAllTasks()', function() {
    it('должна возвращать пустой массив для нового списка', function() {
      expect(todoList.getAllTasks()).to.be.an('array').that.is.empty;
    });

    it('должна возвращать все добавленные задачи', function() {
      todoList.addTask('A');
      todoList.addTask('B');
      todoList.addTask('C');
      expect(todoList.getAllTasks()).to.have.lengthOf(3);
    });
  });
});

Запуск тестов:

npm install --save-dev chai
npx mocha test/todo.test.js

6. Советы по производительности и типичные ошибки

Советы по производительности

  • Используйте --timeout разумно: По умолчанию Mocha ждёт 2000 мс. Для асинхронных тестов, которые выполняются долго (например, работа с сетью), увеличьте таймаут: it('...', function(done) { this.timeout(10000); ... });.
  • Избегайте тяжёлых операций в beforeEach: Если вам нужно подготовить данные для каждого теста, старайтесь делать это максимально быстро. Для дорогих операций (создание БД,

Рекомендаций