Обеспечение изолированного выполнения тестов с использованием Chai Spies

Суть

Используйте крючки beforeEach и afterEach мокко для настройки и сброса шпиона:

// Set up the spy
beforeEach(function() {
  // chai.spy.on([source code], [function names], [mock function])
  chai.spy.on(domUpdates, ['displayName'], () => true);
})
// Reset and tear down the spy
afterEach(function() {
  chai.spy.restore(domUpdates);
})

При этом ни один тест (блок it) не будет зависеть от другого теста или порядка тестов. Как нам туда добраться? Вот еще немного предыстории реализации выше:

Почему шпионы?

В этом посте я предполагаю, что вы используете мокко и чай и что вы уже используете библиотеку chai-spies для шпиона в своем тесте.

В этом примере есть объект domUpdates, содержащий функции, выполняющие манипуляции с DOM. Функция domUpdates.displayName вызывается внутри другой функции с именем changeName (метод класса):

// domUpdates.js
export default {
  displayName: function(name) {
    document.querySelector('#name-display').innerText = name;
  }
}

// Person.js
class Person {
  constructor(name = 'Sam'){
    this.name = name;
  }
  changeName(newName) {
    this.name = newName;
    domUpdates.displayName(this.name);
  }
}

Без шпиона тестирование функции changeName привело бы к ошибке от mocha:

ReferenceError: document is not defined

Добавить шпиона

Чтобы сохранить тестируемость метода changeName, вы можете добавить шпиона для domUpdates.displayName. Добавление шпиона в тесте выглядит так:

// test-file.js
// (Import your other test setup here...)
import spies from 'chai-spies';
chai.use(spies);
// Add a spy for a specific function
chai.spy.on(domUpdates, ['displayName'], () => true);
describe('Change Name', function() {
  it('Change to a new name', function(){
    let person = new Person('Sam');
    changeName('Alex');
    expect(person.name).to.equal('Alex');
    expect(domUpdates.displayName).to.have.been.called(1);
    expect(domUpdates.displayName).to.have.been.called.with('Alex');
  })
  it('Change to a new name with special characters', function(){
    let person = new Person('Sam');
    changeName('S@m');
    expect(person.name).to.equal('S@m');
    expect(domUpdates.displayName).to.have.been.called(1);
    expect(domUpdates.displayName).to.have.been.called.with('S@m');
  })
})

Это должно работать. Проблема в том, что шпион не сбрасывается автоматически для каждого it блока. Так что второй to.have.been.called(1) потерпит неудачу как есть. Чтобы пройти, второй to.have.been.called() должен быть to.have.been.called(2).

// This change would make the test pass
it('Change to a new name with special characters', function(){
  let person = new Person('Sam');
  changeName('S@m');
  expect(person.name).to.equal('S@m');
  expect(domUpdates.displayName).to.have.been.called(2);
  expect(domUpdates.displayName).to.have.been.called.with('S@m');
})

И если мы добавим больше тестов с помощью этого шпиона, нам также потребуется увеличить следующие значения called(). Если тесты каким-либо образом переупорядочиваются в файле, числа в to.have.been.called() придется изменить. Это не изолированный тест, если значения в to.have.been.called() меняются в зависимости от порядка тестов. Это кошмар испытаний!

Mocha Hooks и шпионское восстановление

Вот где могут помочь крючки Mocha и шпионский метод restore! Перед каждым тестом настраивайте шпион. Затем после каждого теста убирайте шпиона так, чтобы to.have.been.called() был изолирован от каждого блока it.

Добавьте хуки в блок описания, содержащий тесты с использованием шпионов:

// Set up the spy
beforeEach(function() {
  // chai.spy.on([source code], [function names], [mock function])
  chai.spy.on(domUpdates, ['displayName'], () => true);
})
// Reset and tear down the spy
afterEach(function() {
  chai.spy.restore(domUpdates);
})

Теперь тесты могут выполняться изолированно, а значения .called() будут количеством вызовов функции только в блоке it.

Вот и все! Chai Spies не моя любимая библиотека для шпионов, но это то, что я сделал, чтобы сделать тесты более дружелюбными.

Если вы ищете другие библиотеки, где вам нужен шпион, рассмотрите Синон или даже отойдите от мокко и рассмотрите встроенный функционал Jest. Я хотел бы услышать о том, как вы используете шпионов!