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

class App extends React.Component {
 constructor() {
 super();
componentDidMount() {
  Api.getContacts().then(data => 
 { this.setState({ contacts: data}) })
 }

Как видите, компонент App не вызывает API сам по себе, а использует для этого метод getContacts(), определенный в модуле Api.

Вызов внешнего API был выделен в отдельный модуль Api, что значительно упрощает тестирование кода, поскольку мы можем вручную написать макет для этого модуля; см. ниже. Кроме того, это «диктует» СУХОЙ дизайн, поскольку мы можем ожидать, что когда будущие разработчики будут добавлять больше вызовов API, они будут делать это в модуле Api, повторно используя части уже существующей логики.

Вот как выглядит Api:

import axios from 'axios';
class Api {
staticgetContacts() {
 return axios.get('/contacts')
 .then((response) => {
 return response.data })
 }
staticaddContact(name, telephone) {
 return axios.post('/contacts/add', {
 "name": name,
 "telephone": telephone,
 })
 .then((response) => { return response.data })
 .catch((error) => { alert("Error in adding contact") })
 }
}

Моделирование модуля API вручную

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

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

Мы можем добиться этого, используя одно из многих решений для имитации с открытым исходным кодом или выбрав один из четырех методов имитации, перечисленных в Документации Jest. Наиболее популярными из них являются Автоматическая имитация — метод, при котором вы создаете макет, чтобы отслеживать вызовы конструктора класса и всех его методов, и Ручная имитация, когда вы пишете новое поведение модуля.

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

Ниже приведен ручной макет модуля Api. Он имеет методы getContacts() и addContact(), как и «настоящий» модуль Api, но вместо реального вызова API он имеет жестко запрограммированные значения return. Жестко запрограммированные значения напоминают то, что ожидается получить от реального вызова API.

import axios from 'axios';
class Api {
static getContacts() {
 return Promise.resolve([
  { name: "Bob", telephone: "11", id: 1 },
 { name: "Anna", telephone: "22", id: 2 }
 ])}
static addContact(name, telephone) {
 return Promise.resolve([
  { name: "Weronika", telephone: "123", id: 3 }
 ])}
export default Api

Как видите, при вызове метода getContacts() модуль Api вместо реального вызова get возвращает Promise, который при разрешении содержит data о двух контактах: Bob и Anna. Метод addContact() работает аналогичным образом.

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

.
├── config
├── utils
│ ├── __mocks__
│ │ └── api.js // mock of real module
│ └── api.js // real module
├── node_modules
└── views

Время испытаний!

И теперь пришло время расплаты, поскольку мы собираемся (наконец-то) протестировать наше приложение!

Первым тестом будет проверка того, появляются ли контакты в DOM при извлечении из API.

Первый выбор, который нам нужно сделать, это какой метод Enzyme мы хотим использовать: shallow или mount? Поскольку и вызов API, и рендеринг контактов происходят внутри самого компонента App - другими словами, нам не нужны функциональные возможности любого другого компонента. для извлечения контактов и их рендеринга — shallow ing будет правильным выбором.

Наша установка будет выглядеть так:

import Adapter from 'enzyme-adapter-react-16';
import axios from 'axios';
import Enzyme, { shallow } from 'enzyme';
import React from 'react';
import Api from '../../Api';
import App from '../App.react';
jest.mock("../Api")
Enzyme.configure({adapter: new Adapter()});
describe('App', () => {
const app = shallow(<App />);
});

Мы используем ручной макет модуля Api, вызывая jest.mock("../Api"). Код Enzyme.configure({adapter: new Adapter()}) необходим для подключения Enzyme к используемой нами версии React — React 16.

Теперь мы можем написать наш первый тест. Просто чтобы проверить, правильно ли работает макет, мы напишем тест, который проверяет состояние app. Этот тест неидеален по двум причинам. Во-первых, тестирование состояния, а не поведения, является плохой практикой, так как это может привести к смешиванию тестов с реализацией и сделает тесты очень чувствительными ко всем изменениям в дизайне кода. Например, если однажды мы решим начать использовать Redux вместо локального состояния, такой тест состояния станет устаревшим и, что более важно, провалится. Во-вторых, проверка того, содержит ли состояние данные из API путем жесткого кодирования возвращаемого значения API, по сути, проверяет ситуацию, которая никогда не произойдет в реальной жизни, потому что вызов реального API является асинхронным, в то время как наш тест выполняется в синхронной экосистеме. . Несмотря на эти важные причины, я считаю, что написание этого теста (и удаление его позже!) является хорошей отправной точкой, чтобы проверить, работает ли макет.

В этом тесте мы ожидаем, что state нашего app будет содержать те же контакты, которые фиктивный Api возвращает в data. Вы можете спросить: «НО КАК!? Мы нигде не обращались к API; все, что мы сделали, это shallowинг компонента App!' Я здесь с ответами, не беспокойтесь. Все потому, что метод shallow вызывает метод componentDidMount жизненного цикла React, внутри которого мы сделали вызов API. Бум!

Хватит тестировать состояние, так как это в любом случае плохая практика: пришло время поведенческих тестов! В приведенном ниже тесте мы больше не ищем изменения в состоянии, а вместо этого проверяем, помещены ли два дочерних элемента в contacts-list div после того, как приложение будет mounted. Мы ожидаем два контакта, потому что это количество объектов, возвращенных из макета Api.

describe('app', () => {
 it('has contacts in the conacts-list div', () => {
 expect
(app.find('.contacts-list').children().length).toEqual(2);
 })
 })

Чтобы убедиться, что дочерние элементы действительно являются контактами, мы можем проверить, имеют ли они ожидаемый класс HTML:

expect(app.find('.contacts-list').children().filter(".single-contact").length).toEqual(2);

Чтобы быть еще более конкретным, мы можем проверить точный текст контактов в contacts-list, выполнив следующий тест:

expect(app.find('.contacts-list').render().text()).toEqual(
 "1. Bob 11 ✍ 💾 ❌ 2. Anna 22 ✍ 💾 ❌ "
);

В приведенном выше коде я использовал другой метод Enzyme — render(). Этот метод используется для рендеринга компонентов React в статический HTML, что позволяет нам легко исследовать содержимое визуализируемого элемента.

Первоначально опубликовано на https://medium.com 21 ноября 2018 г.