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

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

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

Начать курс

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

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

Sinon.js — это мощная библиотека для JavaScript, которая предоставляет автономные тестовые дублёры: шпионы (spies), стабы (stubs) и моки (mocks). Она не зависит от конкретного тестового фреймворка (Mocha, Jest, Jasmine и др.) и может использоваться с любым из них. Основная цель Sinon — изолировать тестируемый код от внешних зависимостей (базы данных, API, файловой системы, таймеров), позволяя проверять поведение функций в контролируемом окружении.

Зачем это нужно? В реальных приложениях функции часто вызывают другие функции, обращаются к внешним сервисам или работают с асинхронными операциями. Без Sinon вы не сможете легко проверить, была ли вызвана функция обратного вызова, с какими аргументами, сколько раз, или подменить ответ от сервера. Sinon решает эти проблемы элегантно и лаконично, делая тесты быстрыми, надёжными и предсказуемыми.

Библиотека особенно популярна в экосистеме Node.js, где модульное тестирование критически важно. Она позволяет тестировать функции, не запуская реальные HTTP-запросы, не обращаясь к базе данных и не дожидаясь таймеров. Это ускоряет выполнение тестов в десятки раз и делает их детерминированными.

2. Установка

Sinon устанавливается через npm. Для Node.js выполните команду:

npm install sinon --save-dev

Если вы используете TypeScript, установите также типы:

npm install sinon @types/sinon --save-dev

Для использования в браузере можно подключить CDN-версию:

<script src="https://cdn.jsdelivr.net/npm/sinon@latest/pkg/sinon.js"></script>

После установки импортируйте библиотеку в вашем тестовом файле:

const sinon = require('sinon'); // CommonJS
// или
import sinon from 'sinon'; // ES Modules

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

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

const sinon = require('sinon');

// Функция, которую мы тестируем
function greet(name, callback) {
  callback(`Hello, ${name}!`);
}

// Тест с использованием шпиона (spy)
const callbackSpy = sinon.spy();
greet('Alice', callbackSpy);

console.log(callbackSpy.calledOnce);    // true
console.log(callbackSpy.calledWith('Hello, Alice!')); // true

Этот пример демонстрирует базовую идею: мы создаём шпион, передаём его в тестируемую функцию, а затем проверяем, как он был вызван. В реальных тестах вы будете использовать утверждения (assertions) из вашего тестового фреймворка.

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

4.1. sinon.spy() — Шпион

Сигнатура: sinon.spy([object], [method]) или sinon.spy([func])

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

Пример:

const user = {
  setName: function(name) { this.name = name; }
};

const spy = sinon.spy(user, 'setName');
user.setName('John');

console.log(spy.calledOnce);      // true
console.log(spy.calledWith('John')); // true
console.log(spy.firstCall.args);  // ['John']

spy.restore(); // обязательно восстановить оригинальный метод

4.2. sinon.stub() — Стаб (заглушка)

Сигнатура: sinon.stub([object], [method])

Что делает: Создаёт заглушку, которая заменяет функцию и позволяет предопределить её поведение: возвращать конкретное значение, выбрасывать исключение, вызывать колбэк с определёнными аргументами. Стаб также записывает информацию о вызовах, как шпион.

Пример:

const db = {
  findUser: function(id) { /* реальный запрос к БД */ }
};

const stub = sinon.stub(db, 'findUser');
stub.withArgs(1).returns({ id: 1, name: 'Alice' });
stub.withArgs(2).throws(new Error('User not found'));

console.log(db.findUser(1)); // { id: 1, name: 'Alice' }
console.log(db.findUser(2)); // Error: User not found

stub.restore();

4.3. sinon.mock() — Мок

Сигнатура: sinon.mock(object)

Что делает: Создаёт мок для объекта. Мок — это более строгий дублёр: он не только заменяет методы, но и задаёт ожидания (expectations) — какие методы должны быть вызваны, с какими аргументами, сколько раз. Если ожидания не совпадают, мок выбрасывает ошибку при проверке.

Пример:

const api = {
  fetchData: function(url) { /* реальный запрос */ }
};

const mock = sinon.mock(api);
mock.expects('fetchData')
  .once()
  .withArgs('/users')
  .returns([{ id: 1 }]);

// Тестируемый код
api.fetchData('/users');

mock.verify(); // Проверка: если ожидания не выполнены — ошибка
mock.restore();

4.4. sinon.fake() — Фейк (замена)

Сигнатура: sinon.fake([func]) или sinon.fake.returns(value) / sinon.fake.throws(error)

Что делает: Создаёт простую фейковую функцию, которая может возвращать значение, выбрасывать исключение или выполнять пользовательскую логику. В отличие от stub, fake не имеет методов для проверки вызовов (но их можно добавить через sinon.spy(fake)).

Пример:

const fakeSuccess = sinon.fake.returns('OK');
console.log(fakeSuccess()); // 'OK'

const fakeError = sinon.fake.throws(new Error('Fail'));
// fakeError(); // Error: Fail

// Комбинируем со шпионом
const spyOnFake = sinon.spy(sinon.fake.returns(42));
console.log(spyOnFake()); // 42
console.log(spyOnFake.calledOnce); // true

4.5. sinon.useFakeTimers() — Фейковые таймеры

Сигнатура: sinon.useFakeTimers([now], [config])

Что делает: Заменяет глобальные функции работы со временем (setTimeout, setInterval, Date.now, performance.now и др.) на фейковые, которые можно контролировать вручную. Позволяет "прокручивать" время в тестах без реальных задержек.

Пример:

const clock = sinon.useFakeTimers();

let callbackCalled = false;
setTimeout(() => { callbackCalled = true; }, 1000);

console.log(callbackCalled); // false
clock.tick(1000);            // прокручиваем время на 1 секунду
console.log(callbackCalled); // true

clock.restore();

4.6. sinon.fakeServer — Фейковый сервер (XHR)

Сигнатура: sinon.fakeServer.create([config])

Что делает: Создаёт фейковый сервер для перехвата XMLHttpRequest-запросов в браузере. Позволяет задавать предопределённые ответы на запросы без реального HTTP-вызова.

Пример:

const server = sinon.fakeServer.create();
server.respondWith('GET', '/api/users', [
  200,
  { 'Content-Type': 'application/json' },
  JSON.stringify([{ id: 1, name: 'Alice' }])
]);

// Где-то в коде делается XHR-запрос
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/users');
xhr.onload = function() {
  console.log(JSON.parse(this.responseText)); // [{ id: 1, name: 'Alice' }]
};
xhr.send();

server.respond(); // заставить сервер ответить
server.restore();

4.7. sinon.match() — Сопоставление аргументов

Сигнатура: sinon.match(value) или sinon.match(type), sinon.match.string, sinon.match.number и др.

Что делает: Предоставляет гибкие сопоставители для проверки аргументов. Позволяет проверять не точное равенство, а соответствие типу, регулярному выражению, объекту с определёнными полями и т.д.

Пример:

const stub = sinon.stub();
stub(sinon.match.string, sinon.match.number);
stub(sinon.match({ id: sinon.match.number, name: sinon.match.string }));

console.log(stub.calledWith('hello', 42)); // true
console.log(stub.calledWith({ id: 1, name: 'Alice' })); // true

// Другие полезные сопоставители:
sinon.match.any       // любое значение
sinon.match.defined   // не undefined
sinon.match.array     // массив
sinon.match.regexp(/^test/) // строка, подходящая под регулярку

4.8. sinon.assert — Утверждения

Сигнатура: sinon.assert.method(spy, [arg1, arg2, ...])

Что делает: Набор вспомогательных функций для проверки состояния шпионов, стабов и моков. Если проверка не проходит, выбрасывается исключение с понятным сообщением.

Пример:

const spy = sinon.spy();
spy('arg1', 'arg2');

sinon.assert.calledOnce(spy);
sinon.assert.calledWith(spy, 'arg1', 'arg2');
sinon.assert.calledWithExactly(spy, 'arg1', 'arg2');

// Другие полезные утверждения:
sinon.assert.notCalled(spy);
sinon.assert.callOrder(spy1, spy2);
sinon.assert.alwaysCalledWith(spy, 'arg');

4.9. sinon.createStubInstance() — Стаб для класса

Сигнатура: sinon.createStubInstance(Constructor)

Что делает: Создаёт экземпляр класса, где все методы заменены стабами. Полезно для тестирования классов-зависимостей без вызова реального конструктора.

Пример:

class Database {
  constructor() { /* сложная инициализация */ }
  query(sql) { /* реальный запрос */ }
}

const dbStub = sinon.createStubInstance(Database);
dbStub.query.returns([{ id: 1 }]);

console.log(dbStub.query('SELECT * FROM users')); // [{ id: 1 }]
console.log(dbStub.query.calledOnce); // true

4.10. sinon.replace() и sinon.replaceGetter()/sinon.replaceSetter()

Сигнатура: sinon.replace(object, property, replacement)

Что делает: Заменяет свойство объекта (функцию, геттер, сеттер) на указанное значение. Отличается от stub тем, что может заменить не только функции, но и любые значения.

Пример:

const config = { timeout: 5000 };
sinon.replace(config, 'timeout', 100);

console.log(config.timeout); // 100

sinon.restore(); // восстанавливает все замены

5. Реальный практический пример

Представим, что у нас есть сервис для отправки email-уведомлений и функция регистрации пользователя. Мы хотим протестировать регистрацию, не отправляя реальные письма.

const sinon = require('sinon');

// --- Реальные модули (которые мы будем заменять) ---
const emailService = {
  sendWelcomeEmail: function(userEmail) {
    // Реальная отправка через SMTP
    console.log(`Sending welcome email to ${userEmail}`);
    return { success: true };
  }
};

const userRepository = {
  save: function(user) {
    // Сохранение в БД
    console.log(`Saving user ${user.email}`);
    return { id: 1, ...user };
  }
};

// --- Функция, которую мы тестируем ---
function registerUser(email, password) {
  if (!email || !password) {
    throw new Error('Email and password are required');
  }

  const user = userRepository.save({ email, password });
  emailService.sendWelcomeEmail(email);
  return user;
}

// --- Тест с использованием Mocha (но можно любой фреймворк) ---
describe('registerUser', function() {
  let emailStub, saveStub;

  beforeEach(function() {
    // Создаём стабы для внешних зависимостей
    emailStub = sinon.stub(emailService, 'sendWelcomeEmail').returns({ success: true });
    saveStub = sinon.stub(userRepository, 'save').returns({ id: 1, email: 'test                    

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