Вчера я потратил некоторое время на настройку тестирования в приложении React Native, и это оказалось на удивление непросто.

Я хотел использовать Enzyme с очень простым API для тестирования моих компонентов и Mocha для запуска тестов. Эта установка кажется довольно популярной, но ни одно из найденных мной руководств не работает хорошо с последней версией React Native (0.25 на момент последнего редактирования). В конце концов, то, что сработало для меня, было собрано из нескольких сообщений в блогах и целого ряда проблем с GitHub.

Уже настроено тестирование? Я написал еще один пост о том, как тестировать код, у которого есть зависимости здесь

Пакеты, которые нам нужны

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

  • Babel за транспиляцию нашего javascript
  • Мокко для проведения тестов
  • Чай за наши утверждения
  • Синон для шпионов, заглушек и издевательств.
  • Энзим для приятных компонентных тестов

Чтобы заставить фермент работать, нам также понадобится react-dom, а также react-native-mock, что дает нам почти полностью смоделированную версию React Native.

npm i --save-dev babel-core babel-preset-react-native babel-preset-airbnb mocha chai chai-enzyme [email protected] react-native-mock react-dom sinon

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

Отредактировано, чтобы установить версию фермента 2.2. Я получал ошибки в React Native 0.25, используя RN mocks после того, как был выпущен фермент 2.3. YMMV.

Хитрый бит

Здорово! У нас есть нужные нам пакеты. Но пока не работает.

Есть код ES6, который не переносится, и нам нужно убедиться, что используются макеты, поддерживающие реакцию.

Чтобы это заработало, мы можем создать простой сценарий установки, который будет запускаться перед тестами. Это гарантирует, что babel транспилирует наш код, модуль, поддерживающий реакцию, и некоторые другие наши зависимости. Вам также понадобится файл .babelrc в корне проекта, например:

{
  "presets": [
    "react-native"
  ]
}

Затем создайте установочный файл. Я поместил это в test / setup.js

import fs from 'fs';
import path from 'path';
import register from 'babel-core/register';
import chai from 'chai';
import chaiEnzyme from 'chai-enzyme';
// Ignore all node_modules except these
const modulesToCompile = [
  'react-native',
  'apsl-react-native-button',
  'react-native-router-flux',
  'react-native-tabs',
  'react-native-vector-icons',
  'react-native-mock',
  'react-native-parallax-scroll-view',
  'react-native-simple-store'
].map((moduleName) => new RegExp(`/node_modules/${moduleName}`));
const rcPath = path.join(__dirname, '..', '.babelrc');
const source = fs.readFileSync(rcPath).toString();
const config = JSON.parse(source);
config.ignore = function(filename) {
  if (!(/\/node_modules\//).test(filename)) {
    return false;
  } else {
    const matches = modulesToCompile.filter((regex) => regex.test(filename));
    const shouldIgnore = matches.length === 0;
    return shouldIgnore;
  }
}
register(config);
// Setup globals / chai
global.__DEV__ = true;
global.expect = chai.expect;
chai.use(chaiEnzyme());
// Setup mocks
require('react-native-mock/mock');
const React = require('react-native')
React.NavigationExperimental = {
  AnimatedView: React.View
};

В этом есть три части.

  • Убедитесь, что babel не игнорирует ничего, кроме node_modules или каких-либо модулей, указанных в modulesToCompile.
  • Настройте некоторые глобальные параметры, например "ожидайте", чтобы его не нужно было импортировать повсюду.
  • Включите response-native-mock (я также высмеял небольшую часть NavigationExperimental здесь, поскольку она отсутствует в response-native-mock прямо сейчас)

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

Написание тестов

Наконец, мы можем перейти к хорошему. Напишем тесты! Мы будем использовать этот действительно простой компонент AngryButton в качестве примера.

NB. Я поместил проект с этим компонентом в репозиторий GitHub. Если вы хотите увидеть все вместе, посмотрите его здесь.

class AngryButton extends Component {
  render() {
    const { text, onPress } = this.props;
    return (
      <TouchableHighlight style={styles.button} onPress={onPress}>
        <Text style={styles.text}>{text.toUpperCase()}</Text>
      </TouchableHighlight>
    );
  }
}

Компонент AngryButton принимает два свойства; некоторый текст, который он разозлит, и функция, вызываемая при нажатии кнопки.

Для начала мы просто проверим, что он действительно отображает - если это сработает, значит, мы знаем, что все настроено нормально.

import React, { Text, TouchableHighlight } from "react-native";
import { shallow } from "enzyme";
import AngryButton from "../AngryButton";
describe("<AngryButton/>", () => {
  it("should render", () => {
    const button = shallow(<AngryButton text="bananas" />);
    expect(button.contains(TouchableHighlight)).to.equal(true);
    expect(button.contains(Text)).to.equal(true);
  });
})

Хотя это базовый материал, он также показывает, насколько прост фермент - он делает очевидным то, что делает тест.

Мне нравится, когда мои тесты находятся рядом с тем, что они тестируют, поэтому я храню их в папке __specs__ рядом с компонентом, но вы можете разместить их где угодно. В этом примере это src / components / __ specs __ / AngryButton.spec.js.

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

mocha --require test/setup.js --compilers js:babel-core/register src/**/__specs__/*.spec.js

Если это сработало, вы должны увидеть пройденный тест.

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

import sinon from "sinon";

а затем добавьте тестовые примеры

  it("should capitalise text", () => {
    const button = shallow(<AngryButton text="bananas" />);
    expect(button.find(Text).props().children).to.equal("BANANAS");
  });
  it("should handle button presses", () => {
    const onPress = sinon.spy();
    const button = shallow(<AngryButton text="yo" onPress={onPress}/>);
    button.simulate('press');
    expect(onPress.calledOnce).to.equal(true);
  });

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

Уборка

Мы могли бы оставить это там, но давайте немного приберемся. Mocha позволяет нам взять параметры, которые мы всегда ему передаем, и поместить их в файл параметров. Я поместил их в test / mocha.opts

--require test/setup.js
--compilers js:babel-core/register

Давайте также добавим тестовый скрипт в наш package.json

...
"scripts": {
  "test": "mocha --opts test/mocha.opts src/**/__specs__/*.spec.js"
}
...

Теперь, чтобы запустить наши тесты, мы просто запускаем «npm test».

Все вместе вы можете проверить на примере Github repo. Стоит изучить каждый из пакетов, чтобы лучше понять, на что они способны. Также действительно полезно взглянуть на то, как пакеты React Native с открытым исходным кодом проводят свое тестирование - response-native-simple-auth - хороший пример.

Удачного тестирования 😄.

Мы работаем над электронной книгой React Native. Хотите копию? Подпишитесь на обновления на нашем сайте.