Мы завершаем эту серию, исследуя поведение тестируемых компонентов; и намек на тестирование снимков.

Эта статья является частью серии, начинающейся с Руководство для скептиков по тестированию внешнего интерфейса: Часть 1, в которой изучается тестирование внешнего интерфейса путем постепенного создания простого веб-приложения-калькулятора (с использованием React / Redux ).

Примеры из этой серии доступны для скачивания.

Поведение

В последней статье единственным фрагментом открытого кода был обработчик onClick компонента Button; несколько сомнительно отсутствие 100% покрытия кода.

part4 / src / MyApp / components / Calculator / Button / index.jsx

...
onClick={() => setFirst(number)}
...

Кроме того, если подумать о компоненте Button, он должен делать больше, чем просто отрисовывать себя; также предполагается, что у него есть механизм для вызова свойства функции setFirst со свойством number. Можно сказать, что это часть контракта на компонент.

Между желанием 100% покрытия кода и тестированием контракта компонента мы внесем некоторые улучшения в тесты компонента Button.

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

Во-первых, казалось целесообразным заключить наши тесты в блоки с подходящим названием description (а не просто полагаться на имя файла теста), например, для тестирования компонента Calculator:

/part4/src/MyApp/components/Calculator/Calculator.test.jsx

...
describe('Calculator component', () => {
  it('shallow renders without crashing', () => {
    ..
  });
});

Кроме того, в нашем последнем примере мы повторили код, который неглубоко отрисовывает компонент; эта проблема была бы еще хуже, если бы у нас было много общих свойств, которые мы передали для каждого теста.

/part3/src/MyApp/components/Calculator/Display/Display.test.jsx

...
it('shallow renders without crashing with number', () => {
  shallow(<Display first={1} />);
});
it('shallow renders without crashing with null', () => {
  shallow(<Display first={null} />);
});
...

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

part4 / src / MyApp / компоненты / Калькулятор / Дисплей /Display.test.jsx

import React from 'react';
import Enzyme, { shallow } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import { Display } from '../Display';
Enzyme.configure({ adapter: new Adapter() });
const setup = (propOverrides) => {
  const props = {
    ...propOverrides,
  };
  return ({
    props,
    wrapper: shallow(<Display {...props} />),
  });
};
describe('Display component', () => {
  describe('shallow renders without crashing', () => {
    it('with first property undefined', () => {
      setup({});
    });
    it('with first property number', () => {
      setup({ first: 1 });
    });
    it('with first property null', () => {
      setup({ first: null });
    });
  });
});

Наблюдения:

  • В этом примере не используются возможности общих свойств (в следующем примере это будет).
  • В этом примере не используются возвращенные свойства или оболочка (в следующем примере это будет).

Используя тот же шаблон, мы можем реорганизовать тест компонента Button:

part4 / src / MyApp / компоненты / Калькулятор / Кнопка /Button.test.jsx

import React from 'react';
import Enzyme, { shallow } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import { Button } from '../Button';
Enzyme.configure({ adapter: new Adapter() });
const setup = (propOverrides) => {
  const props = {
    number: 0,
    setFirst: jest.fn(),
    ...propOverrides,
  };
  return ({
    props,
    wrapper: shallow(<Button {...props} />),
  });
};
describe('Button component', () => {
  it('shallow renders without crashing', () => {
    setup({});
  });
  it('calls setFirst on click', () => {
    const NUMBER = 1;
    const { props: { setFirst }, wrapper } = setup({ number: NUMBER });
    wrapper.simulate('click');
    expect(setFirst.mock.calls).toHaveLength(1);
    expect(setFirst.mock.calls[0][0]).toBe(NUMBER);
  });
});

Наблюдения:

  • В этом тесте у нас установлены общие свойства number и setFirst; setFirst - это фиктивная функция Jest.
  • У нас есть второй тест поведения, который нажимает кнопку и гарантирует, что функция setFirst вызывается один раз с правильным значением (свойство number).
  • Если подумать о том, как написано большинство приложений React / Redux, большая часть (если не все) поведения сводится к вызову компонентов, переданных в функциях (создателях действий). Этот тест является представителем этого типа поведенческого тестирования.
  • Наконец, с проведением поведенческого теста у нас теперь есть 100% покрытие (кода, который нас интересует в этом примере).

Тестирование снимков

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

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

- Команда Jest

Возвращаясь к моему беспокойству по поводу тестирования декларативного кода, мне интересно, какова ценность (по крайней мере, в этом примере) моментального тестирования компонентов. Например, если подумать о компоненте Display:

hello-fe-testing / part4 / src / MyApp / компоненты / Калькулятор / Дисплей /index.jsx

import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import * as fromFirst from '../../../ducks/first';
export function Display({ first }) {
  return (
    <div>{first !== null && first}</div>
  );
}
Display.propTypes = {
  first: PropTypes.number,
};
Display.defaultProps = {
  first: null,
};
export default connect(
  state => ({
    first: fromFirst.getFirst(state),
  }),
  null,
)(Display);

Не уверен, в каком сценарии тест снимка поможет мне свести к минимуму будущие ошибки в этом в высшей степени декларативном коде. По сути, здесь можно было бы что-то изменить, только если бы они намеревались это сделать, и это привело бы к сбою теста.

Подведение итогов

В конце концов, я решил не реализовывать полноценное веб-приложение калькулятора (простой пример, представленный здесь, демонстрирует все стратегии тестирования, которые я считал важными). Общие правила были:

  • Проведите интеграционный тест всего приложения.
  • Проведите неглубокий дымовой тест всех компонентов (неподключенных).
  • Протестируйте все редукторы Redux.
  • Протестируйте все поведения в контракте компонента (должно быть все поведение с приложением React / Redux).

В то же время кажется, что тестирование - это тема, по которой существует множество мнений; хотел бы услышать ваше.