В связи с растущей сложностью приложений модульное тестирование становится обязательным. Поскольку мы пишем код на JS, мы можем использовать большинство доступных фреймворков / библиотек для тестирования react/web приложений без особых изменений.

Использование Jest Framework и некоторых дополнительных утилит, таких как фермент, упрощает жизнь разработчика.

Какая польза от тестирования кода пользовательского интерфейса?

Мы все задавали / задавали этот вопрос хотя бы раз. Согласитесь или нет, термин модульное тестирование не был очень популярен среди разработчиков FE до последних лет. Вот некоторые из причин, по которым мы должны писать модульные тесты.

  • Он позволяет выявлять ошибки до того, как команда QA:

Все мы знаем, что тестировщик и разработчик никогда не могут быть друзьями. Ни одному разработчику не нравится, когда команда QA находит ошибку и просит ее исправить. Если ваш код хорошо покрыт тестами, шансов найти ошибки очень мало. Беспроигрышный вариант для обеих сторон?

  • Помогает другим разработчикам лучше понять ваш код.

Во всех тестовых фреймворках есть блок описания, в котором вы определяете, что делает метод, на языке, понятном любому человеку. Мне нужно сказать больше?

  • Реорганизует ваш код:

Вы начнете задавать себе эти вопросы во время кодирования: как я буду тестировать этот код? Как мне убедиться, что каждый из написанных мной методов можно протестировать? Если вы уже задаете эти вопросы при написании кода, то вы - одна из немногих жемчужин.

  • Делает ваш код модульным:

Если ваш код протестирован, вероятность того, что он является модульным, составляет 99%, что означает, что вы можете легко внести изменения в будущем.

  • Облегчает отладку / внедрение изменений, намного:

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

  • НЕ увеличивает время разработки:

Многие из нас оправдываются тем, что написание тестовых примеров увеличит время разработки (даже я делал это раньше). Но поверьте мне, это не так. В процессе разработки почти половина времени тратится на отладку / исправление ошибок. Написание модульных тестов может уменьшить это значение с 50% до менее 20%.

Настройка Jest

Для всех, кто не слышал о шутках, загляните сюда: https://facebook.github.io/jest/

Facebook использует Jest для тестирования всего кода JavaScript, включая приложения React. Одна из философий Jest заключается в предоставлении интегрированного опыта с нулевой конфигурацией. Мы заметили, что когда инженерам предоставляются готовые инструменты, они в конечном итоге пишут больше тестов, что, в свою очередь, приводит к созданию более стабильных и здоровых кодовых баз.

Почему шутка?

  • Минимальная конфигурация.
  • Смотрите только измененные файлы.
  • Быстрый
  • Тестирование снимков (объяснено позже)
  • Покрытие из коробки

4 важных тестовых сценария, которые должен иметь каждый проект

"scripts": { 
  "test": "jest --verbose --coverage", 
  "test:update": "jest --verbose --coverage --updateSnapshot",
  "test:watch": "jest --verbose --watch",
  "coverage": "jest --verbose --coverage"
  "coverage:open: open ./coverage/lcov-report/index.html"
}
  • test: Он просмотрит все тестовые файлы и выполнит их. Эта команда также будет использоваться в предварительных обработках и проверках CI.
  • test:watch: Это позволит просмотреть все тестовые файлы. Это очень полезно при написании тестов и быстро увидеть результат.
  • test:update: эта команда обновит моментальные снимки для всех презентационных компонентов. Если снимка нет, он создаст его за вас.
  • coverage: Как следует из названия, эта команда сгенерирует отчет о покрытии.

Соглашения о тестировании:

Также настоятельно рекомендуется иметь соглашения для тестовых файлов. Вот условности, которым мы следовали:

  • Jest рекомендует иметь папку __test__ в том же месте, где находится тестируемый файл.
  • Условное обозначение для тестового файла - <testFileName>.test.js. если вы пишете тест для abc.component.js, тогда имя тестового файла будет abc.component.test.js.
  • В каждом expect мы сначала пишем имя функции, которую нужно протестировать.

Вот небольшой пример, следующий вышеупомянутым соглашениям:

//counter.util.js 
const counter = (a) => a + 1;
//__test__/counter.util.test.js 
describe('counter: Should increment the passed value', () => { 
  ... 
});

Конфигурация JEST:

Как мы читаем в документации, Jest действительно очень легко настроить. Вам не нужен отдельный файл конфигурации, конфигурация настолько проста, что может поместиться только внутри package.json.

Вот наша шутливая конфигурация:

"jest": {
  "preset": "react-native",
  "cacheDirectory": "./cache",
  "coveragePathIgnorePatterns": [ "./app/utils/vendor" ],
  "coverageThreshold": { "global": { "statements": 80 } },
  "transformIgnorePatterns": [ "/node_modules/(?!react-native|react-             navigation)" ] 
}
  • preset: предустановка - это среда узла, которая имитирует среду приложения React Native. Поскольку он не загружает ни DOM, ни API-интерфейсы браузера, он значительно сокращает время запуска Jest.
  • cacheDirectory: Это поможет вам значительно улучшить скорость тестирования. Он делает это путем создания кеша скомпилированных модулей, чтобы в следующий раз не пришлось компилировать node_modules во время выполнения тестов.
  • coveragePathIgnorePatterns: Определите файлы, которые нужно пропустить для отчетов о покрытии.
  • coverageThreshold: определяет пороговое значение для прохождения всех тестов. Если покрытие меньше установленного предела, тесты не пройдут. Это помогло нам сохранить хороший охват в любой момент времени.
  • transformIgnorePatterns: Мы передаем сюда все модули NPM, которые необходимо перенести. Эти модули в основном представляют собой модули ES6 / 7.

Примечание. Не забудьте добавить cache и coverage в свой файл gitignore.

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

Что такое снимки:

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

__tests__/someComponent.component.test.js

import React from 'react';
import renderer from 'react-test-renderer';
import SomeComponent from '../SomeComponent.component';
describe('Some component', () => {
  it('renders correctly', () => {
    const tree = renderer.create(<SomeComponent />).toJSON();
    expect(tree).toMatchSnapshot();
  });
});

Каждый раз, когда Jest видит эту строку expect(tree).toMatchSnapshot();, он создает снимок и сравнивает его с сохраненным снимком. Если снимок отсутствует, Jest сохранит созданный снимок.

Сгенерированный файл оснастки будет выглядеть примерно так:

__tests__/snaphots/someComponent.component.js.snap

exports[`SomeComponent Component: SomeComponent renders correctly 1`] = `
<View 
 style={ 
  Object { 
   "flex": 1, 
  } 
 }
> 
 <Text 
  accessible={true} 
  allowFontScaling={true} 
  ellipsizeMode="tail" 
  style={ 
   Object { 
    "color": "#000000", 
    "fontFamily": "Roboto", 
    "fontSize": 24, 
    "fontWeight": "500", 
    "paddingVertical": 20, 
    "textAlign": "center",
   } 
  } 
 > 
  SomeText 
 </Text> 
</View> 
`;

Как вы можете видеть выше, привязка содержит все возможные свойства пользовательского интерфейса, возвращаемые методом рендеринга.

Должен ли я отправлять сгенерированные снимки в git?

Да, ты должен. Снапсы должны быть на машине каждого разработчика, чтобы, если один из разработчиков неосознанно изменит какой-либо другой компонент, тест сна для этого компонента не удастся, и он / она будет знать, прежде чем нажимать на него. Даже официальная документация Jest говорит об этом:

Ожидается, что все моментальные снимки являются частью кода, который запускается в CI, и, поскольку новые моментальные снимки проходят автоматически, они не должны проходить тестовый запуск в CI-системе. Рекомендуется всегда фиксировать все снимки и держать их под контролем версий.

Что делать, если тест не работает?

Рассмотрим этот сценарий. Вы работали над компонентом, создавали снимок и нажимали его. Позже другой разработчик по имени Джон внес некоторые изменения в компонент. Теперь тест оснастки не удастся, поскольку в оснастке все еще содержится код, который вы написали. Джону нужно будет просто обновить снимок, чтобы тест прошел. Не нужно обновлять тестовый пример, достаточно одной команды: jest --updateSnapshot и все готово.

Мы рекомендуем создать сценарий npm для обновления снимков. Как вы можете видеть в package.json нашего шаблона, он содержит команду под названием «test: update». Эта команда выполняет все тестовые сценарии и обновляет привязку всякий раз, когда это необходимо.

Дополнительную информацию можно найти здесь: https://facebook.github.io/jest/docs/en/snapshot-testing.html#content

Тестирование компонентов с сохранением состояния с помощью Enzyme

Мы говорили о тестировании презентационных компонентов с помощью нашей любимой функции под названием Snaphot testing in Jest. Но он просто проверяет пользовательский интерфейс компонента (только метод рендеринга вашего компонента).

Что, если ваш компонент содержит методы класса? Что делать, если ваш компонент содержит состояние?

Вот где мы используем фермент.

Что такое фермент?

Enzyme - это утилита для тестирования JavaScript для React. В основном вы будете использовать поверхностный фермент. Утилита Shallow помогает нам отрисовывать компонент и позволяет получить доступ к методам / состоянию класса компонента.

Интеграция Enzyme в вашу текущую Jest Framework

Стандартный шаблон react-native поставляется с Jest. Интеграция фермента с Jest - это всего лишь двухэтапный процесс.

  • Установите энзим и jest-энзим yarn add enzyme jest-enzyme --dev
  • Добавьте одну строку в package.json внутри конфигурации jest: "setupTestFrameworkScriptFile": "./node_modules/jest-enzyme/lib/index.js",

Вот и все. Вы можете начать использовать утилиты Enzyme прямо сейчас.

Использование поверхностного рендерера из фермента:

  • Сначала нам нужно выполнить неглубокую визуализацию нашего компонента.
import {shallow} from 'enzyme';
describe('SomeComponent component', () => {
  it('Shallow rendering', () => {
    const wrapper = shallow(<SomeComponent {..props}/>);
  });
});

Теперь наш компонент отрисован, и мы можем получить доступ к свойствам / состоянию / методам с помощью wrapper. Вот как вы получаете к ним доступ:

import {shallow} from 'enzyme';
describe('SomeComponent component', () => {
  it('Shallow rendering', () => {
    const wrapper = shallow(<SomeComponent someProp={1}/>);
    const componentInstance = wrapper.instance();
     //Accessing react lifecyle methods
    componentInstance.componentDidMount();
    componentInstance.componentWillMount();
     //Accessing component state
    expect(wrapper.state('someStateKey')).toBe(true);
     //Accessing component props
    expect(wrapper.props.someProp).toEqual(1);
     //Accessing class methods
    expect(componentInstance.counter(1)).toEqual(2);
  });
});

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

Пример

Давайте возьмем пример компонента с методом состояния и класса. Мы напишем тестовый пример для методов, включая тест на моментальный снимок. Пример включает методы класса тестирования, состояние и свойства.

import React from 'react';
import renderer from 'react-test-renderer';
import {shallow} from 'enzyme';
import Counter from '../Counter.component';
describe('Counter component', () => {
  it('Counter: renders correctly', () => {
    const tree = renderer.create(<Counter />).toJSON();
    expect(tree).toMatchSnapshot();
  });
  it('componentWillMount: should set the passed initialCountValue to state', () => {
    const wrapper = shallow(<Counter initialCountValue={2}/>);
    expect(wrapper.instance().state.count).toBe(2);
  });
  it('incrementCounter: should increment state.count by 1', () => {
    const wrapper = shallow(<Counter initialCountValue={0}/>);
    const instance = wrapper.instance();
    expect(instance.state.count).toBe(0);
    instance.incrementCounter();
    expect(instance.state.count).toBe(1);
  });
  it('decrementCounter: should decrement state.count by 1', () => {
    const wrapper = shallow(<Counter initialCountValue={1}/>);
    const instance = wrapper.instance();
    expect(instance.state.count).toBe(1);
    instance.decrementCounter();
    expect(instance.state.count).toBe(0);
  });
  it('should call props on increment/decrement', () => {
    const incrementSpy = jest.fn();
    const decrementSpy = jest.fn();
    const wrapper = shallow(<Counter initialCountValue={1} onIncrement={incrementSpy} onDecrement={decrementSpy}/>);
    const instance = wrapper.instance();
    instance.incrementCounter();
    expect(incrementSpy).toBeCalledWith(2);
    instance.decrementCounter();
    expect(decrementSpy).toBeCalledWith(1);
  });
});

Первоначально опубликовано на сайте rgabs.github.io 19 мая 2017 г.