Я использую react-hook-form как для веб, так и для react native без единой проблемы. Отличная библиотека. При использовании react-hook-form v6. я столкнулся с проблемой, когда валидация отлично работает в коде, но в тесте объект ошибок всегда пуст даже для неправильного значения. Давайте посмотрим, как решить эту проблему. В этом блоге я покажу, как протестировать react-hook-form с помощью react-native-testing-library для iOS и Android в рамках одного теста с помощью jest-expo, и да, мы будем использовать jest в качестве тестового бегуна.
1. Настройка проекта
Я буду использовать expo для быстрой демонстрации.
# select blank template, JS or TS
expo init test-rhf
cd test-rhf
yarn add react-hook-form
yarn add --dev react-native-testing-library jest-expo
jest-expo — это библиотека от команды Expo для универсального тестирования, она будет запускать ваши тесты для каждой платформы, которую вы установите, здесь, поскольку react-native-testing-library поддерживает только нативные устройства, мы не будем использовать веб-установку, через expo’s file extension pick up и сходство между react-native-testing-library и @testing-library/react, поделиться одним единственным тестовым файлом для веб- и нативных устройств должно быть легко, попробуем позже.
В package.json:
-
добавьте скрипт: «test»: «node_modules/.bin/jest».
-
добавьте настройки jest:
"jest": {
"projects": [
{
"preset": "jest-expo/ios",
"setupFilesAfterEnv": [
"<rootDir>/jestAfterEnvSetup.js"
]
},
{
"preset": "jest-expo/android",
"setupFilesAfterEnv": [
"<rootDir>/jestAfterEnvSetup.js"
]
}
]
}
Если вы хотите переписать какие-либо правила jest, вы должны написать новое правило для каждой платформы, как в примере выше.
создайте jestAfterEnvSetup.js:
global.window = {};
global.window = global;
Если вы используете Typescript, вам может понадобиться // @ts-ignore вышеприведенные строки
2. Приложение для тестирования
Измените файл App.js на следующий:
import React from "react";
import { Text, Button, TextInput, View } from "react-native";
import { useForm, Controller } from "react-hook-form";
export default function App() {
const { errors, control, handleSubmit } = useForm({
defaultValues: { name: "" },
});
const errorText = errors["name"]?.message;
const isError = Boolean(errorText);
return (
<View style={{ margin: 10 }}>
<Controller
control={control}
render={({ onChange, onBlur, value }) => (
<TextInput
style={{ borderColor: "black" }}
testID="nameInput"
onChangeText={onChange}
onBlur={onBlur}
value={value}
/>
)}
rules={{ required: "name can't be blank" }}
name="name"
/>
{isError && <Text testID="nameErrorText">{errorText}</Text>}
<Button
testID="submitButton"
title="submit"
onPress={handleSubmit(async ({ name }) => {
console.log(name);
})}
/>
</View>
);
}
У нас есть простая форма, один текстовый ввод для имени, и он обязателен, одна кнопка отправки для отправки значений.
Запустив expo, вы увидите это уродливое приложение, нажатие кнопки submit с пустым вводом приведет к ошибке.
Мы извлекаем errorText из errors[«name»]?.message, затем используем Boolean(errorText) для проверки, если есть текст ошибки, значит ошибка есть.
3. Тест
Создайте файл App.test.js на том же уровне App.js со следующим содержимым:
import * as React from "react";
import App from "./App";
import { render, fireEvent, act } from "react-native-testing-library";
it("should not trigger error for correct values", async () => {
const { getByTestId, queryByTestId } = render(<App />);
fireEvent.changeText(getByTestId("nameInput"), "ABCDEFG");
await act(async () => {
fireEvent.press(getByTestId("submitButton"));
});
expect(queryByTestId("nameErrorText")).not.toBeTruthy();
});
it("should trigger error for empty input", async () => {
const { getByTestId, queryByTestId } = render(<App />);
await act(async () => {
fireEvent.press(getByTestId("submitButton"));
});
expect(queryByTestId("nameErrorText")).toBeTruthy();
});
Здесь у нас есть 2 теста, один для счастливого пути, а другой для несчастливого.
Мы будем имитировать поведение пользователя.
Для счастливого пути: Если у нас есть значение, то текст nameErrorText не должен отображаться.
Для несчастливого пути: Если у нас нет значения, то приложение должно показать текст nameErrorText.
Тесты должны быть довольно простыми для чтения. Я не буду объяснять их здесь.
Самая интересная часть:
await act(async () => {
fireEvent.press(getByTestId("submitButton"));
});
Почему нам нужно ожидать act(async()=>{}) события нажатия кнопки? Потому что валидация в react-hook-form всегда асинхронна, поэтому вам придется ждать, пока она завершится. (Что хорошо, потому что в реальном мире валидация может быть дорогостоящей).
Если вы забудете обернуть act(), вы увидите красное предупреждение: Warning: Обновление App внутри теста не было обернуто в act(…).
4. Завершите .
Запустите тест yarn, и вы увидите, что все тесты пройдены.
Спасибо за прочтение! Надеюсь, это поможет.
Следите за мной (albertgao) в twitter, если хотите узнать больше о моих интересных идеях.