Утилита тестирования Airbnb Enzyme помогает упростить тестирование компонентов React, предоставляя API-интерфейс, подобный jQuery, для доступа к элементам DOM. Кроме того, функциональность поверхностного рендеринга Enzyme помогает снизить потребность в чрезмерном имитации зависимостей, что ускоряет написание тестов. Я экспериментировал с разными способами тестирования React, включая Jest, но сейчас я использую следующие инструменты:

  • Энзим (тестовая утилита)
  • Мокко (фреймворк)
  • Чай (библиотека утверждений)
  • Sinon (шпионский инструмент)

Если вы используете Redux и следуете шаблону проектирования контейнер / дисплейный компонент, у вас, вероятно, будет два разных типа компонентов для тестирования - ваш дисплейный компонент, который должен быть довольно простым для тестирования, и ваш контейнерный компонент, который немного сложнее. Давайте сначала поговорим о тестировании компонентов дисплея.

Прежде чем вы сможете запускать какие-либо тесты, вам нужно настроить скрипт для запуска Mocha. Я делаю это, добавляя сценарий test в package.json, который запускает мокко с некоторыми дополнительными параметрами конфигурации. Базовый сценарий может выглядеть примерно так:

{
 “scripts”: {
 “test”: “mocha —-compilers js:babel-core/register —-colors ./src/**/*.spec.js”
 }
}

Это говорит Mocha использовать Babel в качестве компилятора, чтобы он поддерживал любой синтаксис ES6 или не ванильный JS, который я могу использовать. Мне также нравится добавлять флаг «цвета», чтобы неудачные тесты отображались красным цветом. Последняя часть указывает Mocha искать в любой подпапке «src» файлы, оканчивающиеся на «.spec.js».

Теперь вы можете выполнять свои тесты, запустив npm run test в терминале.

Чтобы начать с простого примера, давайте посмотрим на базовый компонент отображения из моего последнего сообщения:

export const MyInputComponent = (props) => (
 <input
  onChange={props.onChange}
  type='text'
  value={props.value}
 />
);

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

Сначала вам нужно импортировать правильные модули в тестовый файл:

// MyInputComponentTest.jsx
import React from 'react';
import { shallow } from 'enzyme';
import chai from 'chai';
import sinon from 'sinon';
import MyInputComponent from './MyInputComponent';

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

describe('<MyInputComponent/>', () => {
    it('renders without exploding', () => {
        const wrapper = shallow(<MyInputComponent />);
        expect(wrapper.find(MyInputComponent).length).to.equal(1);
    });
});

Теперь, когда вы знаете, что компонент, по крайней мере, можно визуализировать без нарушения всего остального, вы можете протестировать его фактическую функциональность. Давайте начнем с того, что убедимся, что оно отображает правильное значение.

...
it('displays the correct value', () => {
 const wrapper = shallow(<MyInputCopmponent value='Foo' />);
 expect(wrapper.text()).to.contain('Foo');
});
...

Пока все выглядит хорошо. Мы подтвердили, что компонент правильно отображает переданное ему свойство value.

Теперь мы проверим, правильно ли выполняется обратный вызов handleChange. Для этого вы можете настроить шпиона с помощью S inon и передать его как опору. Sinon имеет отличный API, который позволяет глубоко погрузиться в оценку поведения функций. В этом случае мы просто хотим убедиться, что он вообще вызывается.

describe('MyInputComponent', () => {
    it('should execute the handleChange callback on change', () => {
    const spy = sinon.spy();
        const wrapper = shallow(
            <MyInputCopmponent handleChange={stub} />
        );
        wrapper.simulate('change');
        expect(spy.calledOnce).to.equal(true);
    });
});

Если бы я использовал библиотеку утверждений sinon-chai, я бы даже мог облегчить чтение, например:

// with sinon-chai
...
expect(spy).to.have.been.calledOnce;
...

Мы написали тесты для всего ожидаемого поведения нашего компонента отображения. Это только половина картины, поскольку большая часть работы выполняется компонентом контейнера (особенно при использовании Redux).

Компоненты контейнера

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

Redux полагается на существование магазина, поэтому я собираюсь использовать redux-mock-store, чтобы смоделировать его для тестирования. Мне также нужно будет импортировать Provider из react-redux. Итак, для тестового файла компонента контейнера мой импорт может выглядеть примерно так:

import React from ‘react’;
import { Provider } from ‘react-redux’;
import configureStore from ‘redux-mock-store’;
import { mount } from ‘enzyme’;
import { expect } from ‘chai’;
import MyComponent from ‘./MyComponent’;
import MyComponentContainer from ‘./MyComponentContainer’;

Первое, что нужно сделать, это создать фиктивный магазин с помощью redux-mock-store:

describe('<MyComponentContainer/>', () => {
    const mockState = {
        myValue: 1
        otherValue: 'test'
    };
    const mockStore = configureStore();
    const store = mockStore(mockState);
...

Затем вы можете отрендерить свой компонент с помощью Enzyme, обернув его в компонент провайдера react-redux и передав его в свое фиктивное хранилище в качестве опоры. Это позволит функционировать любым вызовам connect () в вашем компоненте-контейнере.

let wrapper;
beforeEach(() => {
  wrapper = mount(
    <Provider store={store}>
      <MyComponentContainer>
    </Provider>
  );
});

Обратите внимание на использование обратного вызова forEach, чтобы предотвратить повторение этого кода для каждого теста. Кроме того, в этом случае я использую модуль рендеринга Enzyme mount вместо shallow, чтобы он отображал и мой контейнер, и компоненты дисплея для тестирования.

Теперь вы можете писать свои тесты:

it(‘renders a container component’, () => {
  expect(wrapper.find(MyComponentContainer).length).to.equal(1);
});
it(‘renders a display component’, () => {
  expect(wrapper.find(MyComponent).length).to.equal(1);
});

Последние мысли

Для тестирования компонентов React определенно требуется практика, но Enzyme помогает значительно упростить работу. Вот несколько вещей, о которых следует помнить:

  • Вы можете вызывать методы экземпляра и устанавливать состояние непосредственно из оболочки Enzyme (например, wrapper.setState ({…}))
  • Sinon - это мощный инструмент для тестирования поведения методов вашего компонента. Если метод не предоставляется через опору, вы можете назначить шпион sinon методу через прототип вашего компонента:
describe(‘<MyComponentContainer/>’, () => {
  it(‘executes my component method when a key is pressed’, () => {
    const spy = sinon.spy(MyComponentContainer.prototype, ‘handleKeyDown’)
    const wrapper = mount(<MyComponentContainer/>)
    wrapper.find(‘.my-input’).simulate(‘keyDown’, { keyCode: null })
    expect(spy.calledOnce).to.equal(true)
  });
});
  • Не пытайтесь тестировать каждую часть внутренней функциональности вашего компонента. Вместо этого попробуйте разбить его на отдельные модели поведения и протестировать их.

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