Фрагментов кода, которые будут показаны ниже, недостаточно, чтобы заставить приложение работать, поскольку они показывают только строки, относящиеся к сообщению в блоге.
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
после того, как приложение будет mount
ed. Мы ожидаем два контакта, потому что это количество объектов, возвращенных из макета 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 г.