Это краткое руководство по тестированию реагирующих компонентов с Enzyme + Chai + Mocha. Я расскажу о некоторых основах, таких как рендеринг ваших компонентов и обход HTML, а позже я расскажу о некоторых функциях Mocha / Chai. Эта статья предназначена для использования в качестве краткого справочника с множеством практических примеров, а не полного объяснения. Я буду опускать ссылки внизу и отвечать на комментарии, если вам нужно более подробное объяснение.

Фермент: создание оболочки с помощью Shallow, Mount и Render

Прежде чем вы сможете начать делать утверждения и тестировать компонент, вы должны смоделировать его представление. Это означает создание так называемой «оболочки», вы можете установить оболочку одним из следующих трех способов:

  • Рендеринг с мелким: полезно для тестирования вашего компонента как блока, неглубокий гарантирует, что вы не делаете утверждений для дочерних компонентов.
  • Рендеринг с mount: идеально подходит для тестирования жизненных циклов компонентов и DOM API.
  • Render with render: отображает статический HTML.
describe('Foo Component', () => {
  it('will render with shallow', () => {
    const wrapper = shallow(<Foo />);
  });
  it('will render with mount', () => {
    const wrapper = mount(<Foo />);
  });
  it('will render with render', () => {
    const wrapper = render(<Foo />);
  });
});

Что касается API, shallow и mount похожи, но я буду указывать на различия по мере их появления. Тестирование с помощьюrender лучше всего подходит для тестирования статических HTML-представлений, и лично я не нахожу его во многих случаях его использования.

Основные ферментные методы

Вот несколько ферментных методов, которые помогут вам управлять визуализированными компонентами и перемещаться по ним.

Найти

Этот метод действительно полезен, когда вам нужно выделить ярлыки, формы или когда вам нужно настроить таргетинг на конкретный div. Это метод перехода по DOM.

describe("Foo Component", () => { 
  it("will have one label", () => { 
    const wrapper = shallow(<Foo />);
    expect(wrapper.find("label")).to.have.length(1);
  });
});

Пример

Когда вы визуализируете свой компонент, Enzyme предоставляет вам инструментарий для управления этим смоделированным экземпляром вашего компонента. Так что, если у «Foo Component» был метод с именем handleFocus(), мы могли бы захотеть запустить этот метод для целей тестирования. Вы бы сделали это так:

describe('Foo', () => {
  it("has a method called handleFocus", () => {
    const wrapper = shallow(<Foo />);
    wrapper.instance().handleFocus(); // calls handleFocus
  });
});

SetProps и SetState

Если вам нужно, вы также можете установить реквизиты и управлять состоянием вашего компонента в тесте. Это также полезно для тестирования жизненных циклов компонентов.

describe("Foo", () => {
  it("can take props & state through Enzyme tools!", () => {
    const wrapper = shallow(<Foo />);
    wrapper.setProps({ foo: "gbenavid" }); // setProps re-renders <Foo />  
    wrapper.setState({ bar: "K1TT3NzRUL3¡!¡!" });
    expect(wrapper.state('bar')).to.equal("K1TT3NzRUL3¡!¡!");
    expect(wrapper.props('foo')).to.equal("gbenavid");
  });
});

Примечание. Если вы хотите протестировать componentDidUpdate()установку оболочки следующим образом:

const wrapper = shallow(<MyComponent />, { lifecycleExperimental: true });

Симулировать

Это действительно круто. У Enzyme есть способ имитировать события. Я здесь монтирую, но вы также можете сделать неглубокую интерпретацию. Это полезно для использования тех методов, которые выполняются только при определенных событиях, например onClick(), onSubmit()… и т. Д. Иногда вам необходимо подключиться, если вы тестируете DOM API и / или ссылки (подробнее об этом позже).

describe("Foo Component", () => {
  it("textarea hits method handleBlur", () => {
    const wrapper = mount(<Foo Component/>);
    wrapper.find("textarea").simulate("blur");
    // simulate also supports 'change', 'focus', 'submit' among others.
    // make assertion here.
  });
});

Simulate также принимает в качестве события необязательный аргумент. Как передать объект с событием (ями):

let e = {target : { value: "some value" }}; wrapper.find("textarea").simulate("blur", e);

Тестирование ссылок и моделирование кликов.

// In your .jsx file
<textarea
  ref="input"
  className="reusable-input"
  style={this.state.inputStyle}
  value={this.props.value}
  onChange={this.handleChange}
  onBlur={this.handleBlur}
  onFocus={this.handleFocus}
  onKeyDown={this.handleKeyDown}
/>

Я мог бы протестировать любую из вышеперечисленных функций, подобную этой, с помощью симуляторов chai.

let spy = chai.spy.on(Foo.prototype, "someFunction");
const wrapper = mount(<ReusableTextArea {...props}/>);
// Again, if you have a function that hits a DOM API then you need to mount
wrapper.find("textarea").simulate("blur");
expect(spy).to.have.been.called.once();

Утверждения!

Chai.js: (библиотека утверждений для фермента)

Затем вы узнаете, как делать утверждения и манипулировать визуализированными компонентами. Пока мы только просмотрели и отрендерили jsx. Теперь мы рассмотрим некоторые базовые функции, предоставляемые Chai, и плагин chai-spies. Надеюсь, это даст вам лучшую основу, прежде чем вы погрузитесь в документацию. Также у chai-spies есть несколько цепных геттеров, которые я не буду описывать напрямую, но вы можете быстро просмотреть все доступные в документации.
Интерфейс шпиона выглядит так: chai.spy(<Obj>, <[method]>)

Я использовал его в приведенных выше примерах, но здесь я объясню, как его реализовать. Следите за методом в компоненте Foo

// to use this pluggin add this to the top of your testing file
const chai = require("chai"), spies = require("chai-spies");
chai.use(spies);
import Foo from "./<path to component>/Foo.jsx";
describe("Foo", () => {
  it("a call to handleBlankSpace will not error", () => {
    let spy = chai.spy(Foo.prototype, "componentWillRecieveProps"); // spy
    const wrapper = mount(<Foo/>); // est. rendition of <Foo/>
    wrapper.setProps({bar: "baz"}); // manipulate you component in some way
    expect(spy).to.have.been.called.once(); // assert expectations/test
  });
});

Создание утверждений на основе свойств и состояния.

// in jsx file
this.state= {
  bar: "red"
}
let changeState = function () {
  this.setState({bar: "blue"});
}
// in testing file:
let props = {
  ...
}
it("handleChange will assert new state", () => {
  const wrapper = shallow(<Foo {...props}/>); // spread obj passes in props obj.
  wrapper.instance.changeState();
  expect(wrapper.state("bar")).to.equal("blue");
});

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

// in your jsx file that exports component Foo
let parentFunction function() {
  this.childFunction();
}
// in your test file 
it("an example of testing inner functions", () => {
  const wrapper = shallow(<Foo {...props}/>);
  let spy = chai.spy.on(wrapper, "childFunction");
  wrapper.instance().parentFunction();   
  expect(spy).to.have.been.called.once();
});

SetTimeouts (и другие функции, богатые мокко): Mocha отвечает за все функции асинхронного тестирования. Есть масса интересных функций, которые можно реализовать с помощью мокко, но запуск setTimeout я считаю особенно полезным.

it("some test that uses setTimeout", (done) => {
  // set the scope of done by passing it as an arg to your it block   
  const wrapper = shallow(<Foo />);
  let spy = chai.spy.on(wrapper, "childFunction");
  wrapper.instance().parentFunction();
  setTimeout(() => {
    expect(wrapper.state("foo")).to.equal("lorem");
    done();
  }, 100);
});

Если done() не вызывается, тогда ваш тест выдаст ошибку, сообщающую, что вы превысили лимит времени.

Второй аргумент, который принимает setTimeout(), - это число. В приведенном выше примере используется 100. Но вам нужно будет найти это число в вашем .jsx файле под функцией, которую вы тестируете. Он будет контролировать, как долго будет ждать ваш тест.

Попался

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

to.be.called.once // don't have evoke for stylistic flare to.be.called() // MUST be evoked otherwise the test will ALWAYS pass.

Если вы тестируете неизменяемые объекты, убедитесь, что вы делаете следующее утверждение:

to.deep.equal({foo:'lorem'});

Статьи по Теме

Enzyme API Docs
Enzyme | Airbnb Engineering