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

Мы собираемся попробовать написать тесты для простого приложения React Tic Tac Toe, используя пакет Jest npm, разработанный Facebook, а также Утилиту тестирования ферментов, созданную AirBnb. Вы можете следовать официальному руководству React documentation по созданию базовой игры Tic Tac Toe или просто использовать окончательный код, предоставленный по той же ссылке.

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

npm install -g yarn

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

Создайте новую папку Components внутри папки / src со следующей структурой:

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

Ваш index.js должен в основном отображать только наш основной компонент-контейнер Game в React DOM:

import React from 'react';
import ReactDOM from 'react-dom';
import Game from './Components/Game/game'
import './index.css';
// ========================================
ReactDOM.render(<Game />, document.getElementById("root"));

Предполагая, что вы следовали инструкциям в документации и использовали пакет create-response-app для создания нового приложения, вы должны знать, что тестовая среда jest уже включена в этом пакете. Установка jest по отдельности нарушит ваши тесты и вызовет следующую ошибку:

TypeError: environment.setup is not a function

Однако для Enzyme требуется отдельная установка, поэтому запустите:

yarn add enzyme enzyme-adapter-react-16 react-test-renderer

Адаптер также необходимо настроить, создав новый файл с именем setUpTests.js и вставив в него следующее содержимое:

import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });

Последний шаг перед тем, как мы наконец приступим к написанию тестов. В каждой из папок Components, помимо файла компонента, создайте тестовые файлы, которые должны называться board.test.js, game.test.js и square.test.js соответственно. Вы можете узнать больше об условных обозначениях тестовых файлов здесь.

Ваша структура папок / src теперь должна выглядеть так:

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

// Game/game.test.js
import React from 'react'
import Game from './game'
import {shallow} from 'enzyme'
it('renders without crashing', () => {
  shallow(<Game />);
});
// Board/board.test.js
import React from 'react'
import Board from './board'
import {shallow} from 'enzyme'
it('renders without crashing', () => {
  shallow(<Board />);
});
// Square/square.test.js
import React from 'react'
import Square from './square'
import {shallow} from 'enzyme'
it('renders without crashing', () => {
  shallow(<Square/>);
});

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

Запустите тесты с помощью npm test в вашем Терминале. Прямо сейчас вы должны увидеть, что 2 теста проходят для Game и Square, но 1 не проходит для Board:

TypeError: Cannot read property '0' of undefined

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

import React from 'react'
import Board from './board'
import {shallow} from 'enzyme'
it('renders without crashing', () => {
  let squares = Array(9).fill(null)
  shallow(<Board squares={squares}/>);
});

Все 3 теста должны быть пройдены сейчас! Обратите внимание, что нет необходимости перезапускать тесты каждый раз, когда было внесено изменение. При выполнении команды npm test среда тестирования jest запускается автоматически с префиксом --watch, который буквально отслеживает изменения файлов и перезапускает тесты, связанные с этими измененными файлами.

Затем давайте проверим, что при щелчке по квадрату доски запускается событие onClick. Поскольку Board визуализирует компоненты Square и передает им событие onClick в качестве опоры, нам нужно будет визуализировать наш компонент Board, а также его дочерние компоненты Square, поэтому мы будем использовать функцию mount (), предоставляемую Enzyme, вместо мелкий (). Нам также нужно будет создать фальшивое событие onClick, которое мы обычно передаем из компонента Game в Board, используя jest mock functionjest.fn(). Мы также воспользуемся преимуществом селектора find () в Enzyme, чтобы найти компонент, который мы собираемся имитировать, используя simulate (). И, наконец, мы увидим, действительно ли был вызван onClick и с какими аргументами использовался Jest toBeCalledWith ().

import React from 'react'
import Board from './board'
import {shallow, mount} from 'enzyme'
it('renders without crashing', () => {
  let squares = Array(9).fill(null)
  shallow(<Board squares={squares}/>);
});
it('calls onClick event on click of a board square', () =>{
  let squares = Array(9).fill(null)
  const onClick = jest.fn();
  let wrapper = mount(<Board squares={squares} onClick={onClick}/>);
  wrapper.find('button.square').first().simulate('click');
  expect(onClick).toBeCalledWith(0)
})

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

Проверьте свой терминал, чтобы убедиться, что этот тест теперь также проходит!

В следующем тесте мы проверим, правильно ли отображается статус игры. А пока мы начнем с простой проверки «Следующий игрок:» и убедимся, что до начала игры следующим игроком будет X, а после первого хода он изменится на O. В этом случае нам придется смонтировать все наши компоненты. снова, поэтому добавьте mount к импорту фермента.

import React from 'react'
import Game from './game'
import {shallow, mount} from 'enzyme'
it('renders without crashing', () => {
  shallow(<Game />);
});
it('renders game status correctly', () => {
  const wrapper = mount(<Game/>)
  const firstPlayer = wrapper.find('div.game-info').children().first().text()
  expect(firstPlayer).toEqual('Next player: X')
const button = wrapper.find('button.square').first()
  button.simulate('click')
  const secondPlayer = wrapper.find('div.game-info').children().first().text()
  expect(secondPlayer).toEqual('Next player: O')
})

В этом случае мы используем метод toEqual (), предоставленный шуткой, чтобы узнать, является ли текстовое содержимое конкретного элемента (div.game-info) тем, что мы ожидаем.

И последнее, что мы должны добавить в нашу проверку статуса, - это объявление победителя по окончании игры. Нам нужно будет смоделировать щелчки по квадратам, указав номер квадрата с помощью метода .at (index) Enzyme, который должен возвращать оболочку вокруг узла с заданным индексом текущей оболочки (‹Game /›) . Поскольку мы уже один раз щелкнули по первому квадрату, чтобы первая половина теста заработала, мы продолжим работу по очереди, начиная со второго хода. Итак, давайте обновим наш тест следующим содержанием:

it('renders game status correctly', () => {
  const wrapper = mount(<Game/>)
  const firstPlayer = wrapper.find('div.game-info').children().first().text()
  expect(firstPlayer).toEqual('Next player: X')
const button = wrapper.find('button.square').first()
  button.simulate('click')
  const secondPlayer = wrapper.find('div.game-info').children().first().text()
  expect(secondPlayer).toEqual('Next player: O')
//player 2
  const turn2 = wrapper.find('button.square').at(1)
  turn2.simulate('click')
  //player 1
  const turn3 = wrapper.find('button.square').at(4)
  turn3.simulate('click')
  //player 2
  const turn4 = wrapper.find('button.square').at(5)
  turn4.simulate('click')
  //player 1
  const turn5 = wrapper.find('button.square').at(8)
  turn5.simulate('click')
  
  const winner = wrapper.find('div.game-info').children().first().text()
  expect(winner).toEqual('Winner: X')
})

Если вы выполнили все правильно, все тесты должны пройти! Если вы думаете, что что-то упустили, вот ссылка на мое репозиторий Github с окончательным кодом .

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

Шутка

Фермент