Модульное тестирование в нативных приложениях React
Опубликовано: 2022-03-10React Native — один из наиболее широко используемых фреймворков для создания мобильных приложений. Это руководство предназначено для разработчиков, которые хотят приступить к тестированию приложений React Native, которые они создают. Мы будем использовать среду тестирования Jest и Enzyme.
В этой статье мы изучим основные принципы тестирования, изучим различные библиотеки для тестирования приложения и узнаем, как тестировать модули (или компоненты) приложения React Native. Работая с приложением React Native, мы закрепим наши знания о тестировании.
Примечание. Базовые знания JavaScript и React Native будут очень полезны при работе с этим руководством.
Что такое модульное тестирование?
Модульное тестирование — это уровень тестирования, на котором тестируются отдельные компоненты программного обеспечения. Мы делаем это, чтобы гарантировать, что каждый компонент работает должным образом. Компонент — это наименьшая тестируемая часть программного обеспечения.
Чтобы проиллюстрировать это, давайте создадим компонент Button
и смоделируем модульный тест:
import React from 'react'; import { StyleSheet, Text, TouchableOpacity } from 'react-native'; function AppButton({ onPress }) { return ( <TouchableOpacity style={[styles.button, { backgroundColor: colors[color] }]} onPress={onPress} > <Text style={styles.text}>Register</Text> </TouchableOpacity> ); } const styles = StyleSheet.create({ button: { backgroundColor: red; borderRadius: 25, justifyContent: 'center', alignItems: 'center', }, text: { color: #fff } }) export default AppButton;
Этот компонент Button
имеет текст и функцию onPress
. Давайте протестируем этот компонент, чтобы увидеть, что такое модульное тестирование.
Во-первых, давайте создадим тестовый файл с именем Button.test.js
:
it('renders correctly across screens', () => { const tree = renderer.create(<Button />).toJSON(); expect(tree).toMatchSnapshot(); });
Здесь мы проверяем, правильно ли отображается наш компонент Button
на всех экранах приложения. Вот что такое модульное тестирование: тестирование компонентов приложения, чтобы убедиться, что они работают должным образом.
Модульное тестирование в нативных приложениях React
Приложение React Native можно протестировать с помощью различных инструментов, некоторые из которых следующие:
- Вебдрайвер
Этот инструмент тестирования с открытым исходным кодом для приложений Node.js также используется для тестирования приложений React Native. - Кошмар
Это автоматизирует тестовые операции в браузере. Согласно документации, «цель состоит в том, чтобы предоставить несколько простых методов, которые имитируют действия пользователя (такие какgoto
,type
иclick
), с API, который кажется синхронным для каждого блока сценария, а не глубоко вложенными обратными вызовами». - Шутка
Это одна из самых популярных библиотек для тестирования, на которой мы и сосредоточимся сегодня. Как и React, он поддерживается Facebook и был создан для обеспечения «нулевой настройки» для максимальной производительности. - Мокко
Mocha — популярная библиотека для тестирования приложений React и React Native. Он стал предпочтительным инструментом тестирования для разработчиков из-за простоты его настройки и использования, а также его скорости. - Жасмин
Согласно документации, Jasmine — это основанная на поведении среда разработки для тестирования кода JavaScript.
Введение в шутку и фермент
Согласно его документации, «Jest — это восхитительная среда тестирования JavaScript с упором на простоту». Работает с нулевой конфигурацией. После установки (с помощью менеджера пакетов, такого как npm или Yarn) Jest готов к использованию, никаких дополнительных установок не требуется.
Enzyme — это среда тестирования JavaScript для приложений React Native. (Если вы работаете с React, а не с React Native, доступно руководство.) Мы будем использовать Enzyme для тестирования модулей вывода нашего приложения. С его помощью мы можем моделировать время выполнения приложения.
Начнем с настройки нашего проекта. Мы будем использовать приложение Done With It на GitHub. Это рынок приложений React Native. Начните с его клонирования, перейдите в папку и установите пакеты, выполнив для npm следующее:
npm install
… или это для пряжи:
yarn install
Эта команда установит все пакеты в нашем приложении. Как только это будет сделано, мы проверим согласованность пользовательского интерфейса нашего приложения с помощью моментальных снимков, описанных ниже.
Снимки и конфигурация Jest
В этом разделе мы проверим действия пользователя и пользовательский интерфейс компонентов приложения, протестировав моментальные снимки с помощью Jest.
Перед этим нам нужно установить Jest и его зависимости. Чтобы установить Jest для Expo React Native, выполните следующую команду:
yarn add jest-expo --dev
Это установит jest-expo
в каталог нашего приложения. Далее нам нужно обновить наш файл package.json
, чтобы иметь тестовый скрипт:
"scripts": { "test" "jest" }, "jest": { "preset": "jest-expo" }
Добавляя эту команду, мы сообщаем Jest, какой пакет зарегистрировать в нашем приложении и где.
Затем мы добавляем в наше приложение другие пакеты, которые помогут Jest провести всестороннее тестирование. Для npm запустите это…
npm i react-test-renderer --save-dev
… и для пряжи это:
yarn add react-test-renderer --dev
Нам еще нужно немного настроить наш файл package.json
. Согласно документации Expo React Native, нам нужно добавить конфигурацию transformIgnorePattern
, которая предотвращает запуск тестов в Jest всякий раз, когда исходный файл соответствует тесту (т. е. если тест выполнен и в node modules
проекта найден похожий файл).
"jest": { "preset": "jest-expo", "transformIgnorePatterns": [ "node_modules/(?!(jest-)?react-native|react-clone-referenced-element|@react-native-community|expo(nent)?|@expo(nent)?/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|@sentry/.*)" ] }
Теперь давайте создадим новый файл с именем App.test.js
, чтобы написать наш первый тест. Мы проверим, есть ли у нашего App
один дочерний элемент в дереве:
import React from "react"; import renderer from "react-test-renderer"; import App from "./App.js" describe("<App />", () => { it('has 1 child', () => { const tree = renderer.create(<App />).toJSON(); expect(tree.children.length).toBe(1); }); });
Теперь запустите yarn test
или его эквивалент npm. Если в App.js
есть один дочерний элемент, наш тест должен пройти, что будет подтверждено в интерфейсе командной строки.
В приведенном выше коде мы импортировали React
и react-test-renderer
, который рендерит наши тесты для Expo
. Мы преобразовали дерево компонентов <App />
в JSON, а затем попросили Jest посмотреть, соответствует ли возвращаемое количество дочерних компонентов в JSON ожидаемому.
Больше снэпшот-тестов
Как утверждает Дэвид Аденай:
«Снимок теста гарантирует, что пользовательский интерфейс (UI) веб-приложения не изменится неожиданно. Он фиксирует код компонента в определенный момент времени, чтобы мы могли сравнить компонент в одном состоянии с любым другим возможным состоянием, которое он может принять».
Это особенно важно, когда в проекте используются глобальные стили, которые используются во многих компонентах. Давайте напишем тест моментального снимка для App.js
, чтобы проверить согласованность его пользовательского интерфейса:
it('renders correctly across screens', () => { const tree = renderer.create( ).toJSON(); expect(tree).toMatchSnapshot(); });
it('renders correctly across screens', () => { const tree = renderer.create( ).toJSON(); expect(tree).toMatchSnapshot(); });
Добавьте это к уже написанным нами тестам, а затем запустите yarn test
(или его эквивалент npm). Если наш тест пройден, мы должны увидеть это:
PASS src/App.test.js √ has 1 child (16ms) √ renders correctly (16ms) Test Suites: 1 passed, 1 total Tests: 2 passed, 2 total Snapshots: 1 total Time: 24s
Это говорит нам о том, что наши тесты прошли и сколько времени они заняли. Ваш результат будет выглядеть аналогично, если тесты пройдены.
Давайте перейдем к мокированию некоторых функций в Jest.
Имитация вызовов API
Согласно документации Jest:
Мок-функции позволяют вам тестировать связи между кодом, стирая фактическую реализацию функции, перехватывая вызовы функции (и параметры, передаваемые в этих вызовах), перехватывая экземпляры функций-конструкторов при создании экземпляра с помощью `new` и разрешая тестировать временная конфигурация возвращаемых значений.
Проще говоря, мокап — это копия объекта или функции без реальной работы этой функции. Он имитирует эту функцию.
Макеты помогают нам тестировать приложения многими способами, но главное преимущество заключается в том, что они уменьшают нашу потребность в зависимостях.
Моки обычно можно выполнять одним из двух способов. Один из них — создать фиктивную функцию, которая внедряется в тестируемый код. Другой — написать фиктивную функцию, которая переопределяет пакет или зависимость, прикрепленную к компоненту.
Большинство организаций и разработчиков предпочитают писать макеты вручную, которые имитируют функциональность и используют поддельные данные для тестирования некоторых компонентов.
React Native включает fetch
в глобальный объект. Чтобы избежать реальных вызовов API в нашем модульном тесте, мы имитируем их. Ниже приведен способ смоделировать все, если не большинство, наших вызовов API в React Native и без необходимости в зависимостях:
global.fetch = jest.fn(); // mocking an API success response once fetch.mockResponseIsSuccess = (body) => { fetch.mockImplementationForOnce ( () => Promise.resolve({json: () => Promise.resolve(JSON.parse(body))}) ); }; // mocking an API failure response for once fetch.mockResponseIsFailure = (error) => { fetch.mockImplementationForOnce( () => Promise.reject(error) ); };
Здесь мы написали функцию, которая пытается получить API один раз. Сделав это, он возвращает промис, а при его разрешении возвращает тело в JSON. Это похоже на фиктивный ответ для неудачной транзакции выборки — он возвращает ошибку.
Ниже показан компонент product
нашего приложения, содержащий объект product
и возвращающий информацию в виде props
.
import React from 'react'; const Product = () => { const product = { name: 'Pizza', quantity: 5, price: '$50' } return ( <> <h1>Name: {product.name}</h1> <h1>Quantity: {product.quantity}</h1> <h1>Price: {product.price}</h1> </> ); } export default Product;
Давайте представим, что мы пытаемся протестировать все компоненты нашего продукта. Прямой доступ к нашей базе данных не является возможным решением. Здесь в игру вступают мокасины. В приведенном ниже коде мы пытаемся имитировать компонент продукта, используя Jest для описания объектов в компоненте.
describe("", () => { it("accepts products props", () => { const wrapper = mount(<Customer product={product} />); expect(wrapper.props().product).toEqual(product); }); it("contains products quantity", () => { expect(value).toBe(3); }); });
Мы используем describe
из Jest, чтобы диктовать тесты, которые мы хотим выполнить. В первом тесте мы проверяем, равен ли объект, который мы передаем, свойствам, которые мы имитировали.
Во втором тесте мы передаем реквизиты customer
, чтобы убедиться, что это продукт и что он соответствует нашим макетам. При этом нам не нужно тестировать все компоненты нашего продукта, и мы также можем предотвратить ошибки в нашем коде.
Имитация внешних запросов API
До сих пор мы проводили тесты вызовов API с другими элементами нашего приложения. Теперь давайте смоделируем внешний вызов API. Мы собираемся использовать Axios. Чтобы протестировать внешний вызов API, мы должны имитировать наши запросы, а также управлять полученными ответами. Мы собираемся использовать axios-mock-adapter
для имитации Axios. Во-первых, нам нужно установить axios-mock-adapter
, выполнив следующую команду:
yarn add axios-mock-adapter
Следующее, что нужно сделать, это создать наши макеты:
import MockAdapter from 'axios-mock-adapter'; import Faker from 'faker' import ApiClient from '../constants/api-client'; import userDetails from 'jest/mockResponseObjects/user-objects'; let mockApi = new MockAdapter(ApiClient.getAxiosInstance()); let validAuthentication = { name: Faker.internet.email(), password: Faker.internet.password() mockApi.onPost('requests').reply(config) => { if (config.data === validAuthentication) { return [200, userDetails]; } return [400, 'Incorrect username and password']; });
Здесь мы вызываем ApiClient
и передаем ему экземпляр Axios, чтобы имитировать учетные данные пользователя. Мы используем пакет с именем faker.js для создания поддельных пользовательских данных, таких как адрес электронной почты и пароль.
Макет ведет себя так, как мы ожидаем от API. Если запрос выполнен успешно, мы получим ответ с кодом состояния 200 для OK. И мы получим код состояния 400 за неверный запрос к серверу, который будет отправлен в формате JSON с сообщением «Неверное имя пользователя и пароль».
Теперь, когда наш макет готов, давайте напишем тест для внешнего запроса API. Как и раньше, мы будем использовать снимки.
it('successful sign in with correct credentials', async () => { await store.dispatch(authenticateUser('[email protected]', 'password')); expect(getActions()).toMatchSnapshot(); }); it('unsuccessful sign in with wrong credentials', async () => { await store.dispatch(authenticateUser('[email protected]', 'wrong credential')) .catch((error) => { expect(errorObject).toMatchSnapshot(); });
Здесь мы проверяем успешный вход в систему с правильными учетными данными, используя собственный async await
JavaScript для хранения наших входных данных. Между тем, функция authenticateUser
из Jest аутентифицирует запрос и проверяет, соответствует ли он нашим предыдущим снимкам. Затем мы проверяем неудачный вход в случае неправильных учетных данных, таких как адрес электронной почты или пароль, и отправляем сообщение об ошибке в качестве ответа.
Теперь запустите yarn test
или npm test
. Я уверен, что все ваши испытания пройдут.
Давайте посмотрим, как тестировать компоненты в библиотеке управления состоянием Redux.
Тестирование действий Redux и редьюсеров с использованием снимков
Нельзя отрицать, что Redux — один из наиболее широко используемых менеджеров состояний для приложений React. Большая часть функциональности Redux включает в себя dispatch
, которая является функцией хранилища Redux, которая используется для запуска изменения состояния приложения. Тестирование Redux может быть сложным, потому что actions
Redux быстро увеличиваются в размерах и сложности. Со снимками Jest это становится проще. Большинство тестов с Redux сводится к двум вещам:
- Чтобы протестировать
actions
, мы создаемredux-mock-store
и отправляем действия. - Чтобы протестировать редьюсеры, мы импортируем
reducer
и передаем ему объект состояния и действия.
Ниже приведен тест Redux со снимками. Мы будем тестировать действия, отправленные путем аутентификации пользователя при SIGN-IN
в систему, и посмотрим, как действие LOGOUT
обрабатывается редуктором user
.
import mockStore from 'redux-mock-store'; import { LOGOUT } from '../actions/logout'; import User from '../reducers/user'; import { testUser } from 'jest/mock-objects'; describe('Testing the sign in authentication', () => { const store = mockStore(); it('user attempts with correct password and succeeds', async () => { await store.dispatch(authenticateUser('[email protected]', 'password')); expect(store.getActions()).toMatchSnapshot(); }); }); describe('Testing reducers after user LOGS OUT', () => { it('user is returned back to initial app state', () => { expect(user(testUser, { type: LOGOUT })).toMatchSnapshot(); }); });
В первом тесте мы описываем аутентификацию при входе и создаем фиктивное хранилище. Мы делаем это, сначала импортируя mockStore
из Redux, а затем импортируя метод с именем testUser
из Jest, чтобы помочь нам имитировать пользователя. Затем мы проверяем, когда пользователь успешно входит в приложение, используя адрес электронной почты и пароль, которые совпадают с теми, что есть в нашем хранилище моментальных снимков. Таким образом, моментальный снимок гарантирует, что объекты, которые вводит пользователь, совпадают при каждом запуске теста.
Во втором тесте мы проверяем, когда пользователь выходит из системы. Как только наш снимок редуктора подтверждает, что пользователь вышел из системы, он возвращается к исходному состоянию приложения.
Далее мы тестируем, запустив yarn test
. Если тесты прошли, мы должны увидеть следующий результат:
PASS src/redux/actions.test.js √ user attempts with correct password and succeeds (23ms) √ user is returned back to initial app state (19ms) Test Suites: 1 passed, 1 total Tests: 2 passed, 2 total Snapshots: 2 total Time: 31s
Заключение
С Jest тестирование приложений React Native никогда не было проще, особенно с помощью снимков, которые гарантируют, что пользовательский интерфейс остается согласованным независимо от глобальных стилей. Кроме того, Jest позволяет нам имитировать определенные вызовы API и модули в нашем приложении. Мы можем пойти дальше, протестировав компоненты приложения React Native.
Дополнительные ресурсы
- «Практическое руководство по тестированию нативных приложений React с помощью Jest», Дэвид Аденай, Smashing Magazine
- Шуточная документация
- «Тестирование с помощью Jest», документация Expo React Native
- «Учимся тестировать React Native с помощью Jest», Джейсон Гааре