Тестирование CommonJS модулей

CommonJS — это спецификация для модульной системы в JavaScript, которая широко используется в Node.js. Система модулей CommonJS позволяет разделить код на независимые части, улучшая организацию и поддержку проекта. Для написания модульных тестов к таким модулям часто используется фреймворк Jasmine, который позволяет легко и эффективно проверять корректность работы кода.

Основные принципы CommonJS

Модули в CommonJS основываются на простых принципах:

  • Каждый файл является отдельным модулем.
  • Модули используют module.exports для экспорта функциональности.
  • Для импорта функциональности других модулей используется require.

Пример модуля в CommonJS:

// файл sum.js
function sum(a, b) {
  return a + b;
}

module.exports = sum;

Для использования этого модуля в другом файле:

// файл app.js
const sum = require('./sum');
console.log(sum(2, 3)); // 5

Структура теста в Jasmine

Jasmine предоставляет несколько важных конструкций для организации тестов: describe, it, beforeEach, afterEach.

  • describe — группирует тесты, связанные с конкретным функционалом.
  • it — определяет конкретный тест, который проверяет поведение.
  • beforeEach и afterEach — функции, которые выполняются перед и после каждого теста соответственно.

Для начала работы с Jasmine необходимо установить его в проект. Если это Node.js проект, то можно установить Jasmine через npm:

npm install --save-dev jasmine

После установки необходимо инициализировать Jasmine в проекте:

npx jasmine init

Это создаст необходимую структуру каталогов и файлы для тестов.

Тестирование простых функций

Рассмотрим пример тестирования функции, экспортируемой через module.exports. Допустим, у нас есть функция сложения:

// sum.js
function sum(a, b) {
  return a + b;
}

module.exports = sum;

Теперь напишем тест для этой функции с использованием Jasmine:

// sum.spec.js
const sum = require('./sum');

describe('Функция sum', function() {
  it('должна правильно складывать два числа', function() {
    expect(sum(2, 3)).toBe(5);
    expect(sum(-1, 1)).toBe(0);
    expect(sum(-1, -1)).toBe(-2);
  });
});

В этом тесте проверяется, что функция sum возвращает правильный результат для разных входных значений. Мы используем expect для проверки результатов и метод toBe, чтобы сравнить значения.

Тестирование с зависимостями

Если модуль имеет зависимости от других модулей, нужно правильно мокировать (замещать) их в тестах. Jasmine позволяет использовать spies для отслеживания вызовов функций и мокирования зависимостей.

Допустим, у нас есть модуль, который зависит от другого модуля:

// userService.js
const db = require('./db');

function getUserById(id) {
  return db.query(`SELECT * FROM users WHERE id = ${id}`);
}

module.exports = getUserById;

Для того чтобы протестировать getUserById, нужно замокать модуль db, чтобы избежать реальных запросов к базе данных:

// userService.spec.js
const getUserById = require('./userService');
const db = require('./db');

// Мокируем метод query
spyOn(db, 'query').and.returnValue(Promise.resolve([{ id: 1, name: 'John Doe' }]));

describe('userService', function() {
  it('должен возвращать пользователя по ID', function(done) {
    getUserById(1).then(user => {
      expect(user[0].name).toBe('John Doe');
      done();
    });
  });
});

В данном примере используется spyOn для замены функции db.query на поддельную версию, которая всегда возвращает обещание с заранее заданными данными. Это позволяет тестировать логику без взаимодействия с реальной базой данных.

Тестирование асинхронных функций

Jasmine поддерживает тестирование асинхронных функций, таких как промисы и обратные вызовы. Чтобы Jasmine корректно обрабатывал асинхронный код, нужно использовать функцию done, передаваемую в тест.

Пример асинхронного теста:

// asyncExample.js
function fetchData() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('Данные получены');
    }, 1000);
  });
}

module.exports = fetchData;

Тест для асинхронной функции:

// asyncExample.spec.js
const fetchData = require('./asyncExample');

describe('fetchData', function() {
  it('должна возвращать данные после задержки', function(done) {
    fetchData().then(data => {
      expect(data).toBe('Данные получены');
      done();
    });
  });
});

В этом тесте используется промис, который возвращает строку через 1 секунду. Мы проверяем результат с помощью expect и вызываем done, чтобы Jasmine знал, что тест завершился.

Мокирование и шпионы

Мокирование и шпионы — это важные инструменты для создания изолированных тестов, когда нужно заменить реальные реализации зависимостей на поддельные или отслеживать вызовы функций.

Мокирование позволяет заменять объекты или методы, которые вызываются внутри тестируемой функции. Это важно, когда нужно избежать внешних зависимостей, таких как файловая система или API.

Пример шпиона:

// logger.js
function log(message) {
  console.log(message);
}

module.exports = log;
// logger.spec.js
const log = require('./logger');

describe('log', function() {
  it('должна вызывать console.log', function() {
    spyOn(console, 'log');
    log('Test message');
    expect(console.log).toHaveBeenCalledWith('Test message');
  });
});

В этом примере мы использовали spyOn для отслеживания вызова console.log внутри функции log. Это позволяет убедиться, что метод был вызван с правильным аргументом.

Резюме

Тестирование CommonJS модулей с использованием Jasmine позволяет легко и эффективно проверять корректность работы кода, даже если он зависит от других модулей или выполняет асинхронные операции. Основные инструменты Jasmine, такие как describe, it, beforeEach, afterEach, а также мокирование и шпионы, позволяют создавать гибкие и надежные тесты.