Написание React Test с библиотеками React рекомендуют - Jest и React Testing Library для полного новичка.
Эта статья предназначена для тех, кто только начинает изучать React и задается вопросом, как написать несколько простых тестов с помощью своих приложений React. И j. Подобно тому, как большинство людей начинают создавать приложение React, используя create-react-app
, я бы тоже начал с него.
Во-первых, давайте начнем с примера по умолчанию.
Зависимости по умолчанию с create-react-app
(22.05.2020)
"dependencies": {
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-scripts": "3.4.1"
}
Уже написан один тест, который поможет вам начать.
// src/App.test.js import React from 'react'; import { render } from '@testing-library/react'; import App from './App';
test('renders learn react link', () => { const { getByText } = render(<App />); //render is from @testing-library/react const linkElement = getByText(/learn react/i); expect(linkElement).toBeInTheDocument(); //expect assertion is from Jest });
Если вы запустите команду $ yarn test App
, вы увидите результат, аналогичный следующему:
С настройкой по умолчанию
create-react-app
вы можете начать писать тест, ничего не устанавливая и не настраивая.
Из приведенного выше примера мы должны узнать -
- Где и как я могу разместить свои тестовые файлы? - как вы можете видеть,
App.test.js
помещается рядом сApp.js
в той же папке, а суффикс.test.js
после имени компонентаApp
в качестве имени файла. Это соглашения по умолчанию, предложенные командойcreate-react-app
(ссылка здесь). - Jest и React Testing Library - это цепочка инструментов, стоящая за тестом, они оба поставляются с приложением create-react-app по умолчанию.
// setupTests.js
// Jest is importing from a global setup file if you wonder
import '@testing-library/jest-dom/extend-expect';
Во-вторых, напишите тест для компонента NavBar.
Я создаю NavBar
компонент, содержащий ссылки и логотип.
Во-первых, я бы начал писать тест без написания самого компонента (Test Drive Development).
// navBar.test.js import React from 'react'; // screen newer way to utilize query in 2020 import { render, screen } from '@testing-library/react'; import NavBar from './navBar'; // component to test
test('render about link', () => { render(<NavBar />); expect(screen.getByText(/about/)).toBeInTheDocument(); })
Сначала тест завершится неудачно, поскольку я еще не писал кода для navBar.js
компонента.
Тогда давайте закончим компонентnavBar.js
, теперь тест должен пройти.
// navBar.js import React from 'react';
const NavBar = () => ( <div className="navbar"> <a href="#"> about </a> </div> );
export default NavBar;
А пока вам следует узнать:
expect( ... ).toBeInTheDocument()
утверждение от Jest.render(<NavBar />);
иscreen.getByText(/about/)
из библиотеки тестирования.- Библиотека тестирования Jest и React работает вместе, чтобы упростить написание тестов в React.
screen.getByText(/about/)
использование getByText вместо выбора по имени класса связано с тем, что библиотека тестирования React адаптирует образ мышления, ориентированный на взаимодействие с пользователем, а не на детали реализации.
Чтобы узнать больше о расширении и изменении теста, вы можете ознакомиться со следующими ресурсами:
- Учебное пособие по тестированию Jest с приложением React
- Шпаргалка по синтаксису библиотеки тестирования React
Теперь давайте расширим тест и компонент, чтобы сделать его более реальным -
// navBar.test.js import React from 'react'; import { render, screen } from '@testing-library/react'; import NavBar from './navBar';
// include as many test cases as you want here const links = [ { text: 'Home', location: "/" }, { text: 'Contact', location: "/contact" }, { text: 'About', location: "/about" }, { text: 'Search', location: "/search" }, ];
// I use test.each to iterate the test cases above test.each(links)( "Check if Nav Bar have %s link.", (link) => { render(<NavBar />); //Ensure the text is in the dom, will throw error it can't find const linkDom = screen.getByText(link.text); //use jest assertion to verify the link property expect(linkDom).toHaveAttribute("href", link.location); } );
test('Check if have logo and link to home page', () => { render(<NavBar />); // get by TestId define in the navBar const logoDom = screen.getByTestId(/company-logo/); // check the link location expect(logoDom).toHaveAttribute("href", "/"); //check the logo image expect(screen.getByAltText(/Company Logo/)).toBeInTheDocument(); });
Так обычно выглядит компонент NavBar (возможно, потребуется добавить несколько стилей).
// navBar.js import React from 'react';
const NavBar = () => ( <div className="navbar"> <a href="/" data-testid="company-logo"> <img src="/logo.png" alt="Company Logo" /> </a>
<ul> <li> <a href="/"> Home </a> </li> <li> <a href="/about"> About </a> </li> <li> <a href="/contact"> Contact </a> </li> <li> <a href="/search"> Search </a> </li> </ul> </div> );
export default NavBar;
Хорошо, это кажется забавным, давайте напишем более сложный.
В-третьих, напишите тест компонента формы регистрации.
После написания теста на статический контент давайте напишем тест на более динамичный контент - форму регистрации.
Во-первых, давайте подумаем в духе TDD - что нам нужно в этой форме регистрации (независимо от того, как она выглядит):
- Поле ввода для имени, которое допускает длину только строки от 3 до 30.
- Поле ввода для электронной почты, которое может проверить, является ли это действительным адресом электронной почты.
- Поле ввода пароля, в котором можно проверить его сложность (минимум 1 число, 1 строка в нижнем регистре, 1 строка в верхнем регистре, 1 специальный символ)
- Кнопка отправки.
- Все 3 поля, указанные выше, являются обязательными, не могут быть пустыми.
А теперь давайте напишем тест.
/* Prepare some test cases, ensure 90% edge cases are covered. You can always change your test cases to fit your standard */
const entries = [ { name: 'John', email: 'john_doe@yahoo', password: 'helloworld' }, { name: 'Jo', email: 'jo.msn.com', password: 'pa$$W0rd' }, { name: '', email: '[email protected]', password: '123WX&abcd' }, { name: 'kent'.repeat(10), email: '[email protected]', password: 'w%oRD123yes' }, { name: 'Robert', email: '[email protected]', password: 'r&bsEc234E' }, ]
Затем создайте череп теста.
// signupForm.test.js // this mostly a input validate test describe('Input validate', () => { /* I use test.each to iterate every case again I need use 'async' here because wait for validation is await function */
test.each(entries)('test with %s entry', async (entry) => {
... }) })
Теперь давайте построим блок внутри теста.
// signupForm.test.js ... test.each(entries)('test with %s entry', async (entry) => { //render the component first (it will clean up for every iteration render(<SignupForm />); /* grab all the input elements. I use 2 queries here because sometimes you can choose how your UI look (with or without Label text) without breaking the tests */ const nameInput = screen.queryByLabelText(/name/i) || screen.queryByPlaceholderText(/name/i); const emailInput = screen.getByLabelText(/email/i) || screen.queryByPlaceholderText(/email/i); const passwordInput = screen.getByLabelText(/password/i) || screen.queryByPlaceholderText(/password/i); /* use fireEvent.change and fireEvent.blur to change name input value and trigger the validation */ fireEvent.change(nameInput, { target: { value: entry.name } }); fireEvent.blur(nameInput);
/* first if-statement to check whether the name is input. second if-statement to check whether the name is valid. 'checkName' is a utility function you can define by yourself. I use console.log here to show what is being checked. */ if (entry.name.length === 0) { expect(await screen.findByText(/name is required/i)).not.toBeNull(); console.log('name is required.'); } else if (!checkName(entry.name)) { // if the name is invalid, error msg will showup somewhere expect(await screen.findByText(/invalid name/i)).not.toBeNull(); console.log(entry.name + ' is invalid name.'); }; // With a similar structure, you can continue building the rest of the test. ...
/* Remember to add this line at the end of your test to avoid act wrapping warning. More detail please checkout Kent C.Dodds's post: (He is the creator of Testing Library) <https://kentcdodds.com/blog/fix-the-not-wrapped-in-act-warning> */ await act(() => Promise.resolve()); }) ...
Полный код тестирования можно найти здесь.
Хорошо, теперь тест завершен (возможно, мы вернемся, чтобы немного подправить, но давайте пока продолжим), давайте напишем компонент.
// signupForm.js import React from 'react'; /* I borrow the sample code from formik library with some adjustments <https://jaredpalmer.com/formik/docs/overview#the-gist> */ import { Formik } from 'formik'; /* For validation check, I wrote 3 custom functions. (I use the same functions in test) */ import { checkName, checkEmail, checkPassword, } from '../utilities/check';
const SignupForm = () => ( <div> <h1>Anywhere in your app!</h1> <Formik initialValues={{ name: '', email: '', password: '' }} validate={values => { const errors = {}; if (!values.name) { errors.name = 'Name is Required' } else if (!checkName(values.name)) { errors.name = `invalid name`; }
if (!values.email) { errors.email = 'Email is Required'; } else if (!checkEmail(values.email)) { errors.email = 'Invalid email address'; }
if (!values.password) { errors.password = 'Password is Required'; } else if (!checkPassword(values.password)) { errors.password = 'Password is too simple'; }
return errors; }} onSubmit={(values, { setSubmitting }) => { setTimeout(() => { alert(JSON.stringify(values, null, 2)); setSubmitting(false); }, 400); }} > {({ values, errors, touched, handleChange, handleBlur, handleSubmit, isSubmitting, /* and other goodies */ }) => ( <form onSubmit={handleSubmit}> <label> Name: <input type="text" name="name" placeholder="Enter your name here" onChange={handleChange} onBlur={handleBlur} value={values.name} /> </label>
<p style={{ 'color': 'red' }}> {errors.name && touched.name && errors.name} </p>
<label> Email: <input type="email" name="email" placeholder="Your Email Address" onChange={handleChange} onBlur={handleBlur} value={values.email} /> </label> <p style={{ 'color': 'red' }}> {errors.email && touched.email && errors.email} </p>
<label> Password: <input type="password" name="password" placeholder="password here" onChange={handleChange} onBlur={handleBlur} value={values.password} /> </label>
<p style={{ 'color': 'red' }}> {errors.password && touched.password && errors.password} </p>
<button type="submit" disabled={isSubmitting}> Submit </button> </form> )} </Formik> </div> );
export default SignupForm;
И форма будет выглядеть примерно так, как показано ниже (без особого стиля, но достаточно хорошо для нашей цели).
И при неправильном вводе сообщение об ошибке будет отображаться под вводом:
Если вы завершили тест, описанный выше, теперь все тесты должны пройти, запустить yarn test --verbose
, с параметром подробного вывода и сообщением console.log, вы можете увидеть, как тестируется каждый случай, и какой из них является хорошим, а какой нет.
Чтобы увидеть больше примеров кода тестирования и различных случаев, пожалуйста, посмотрите мое репо здесь.
Заключительные слова.
Новичку сложно выучить все сразу, поэтому просто не спешите, если это слишком сложно. На изучение основ у меня ушла как минимум целая неделя, и это только начало написания тестов для приложений React.
Это трудная тема для понимания, но я считаю, что стоит потратить на нее некоторое время, если вы хотите стать Pro FrontEnd разработчиком.
И хорошая новость в том, что у вас хорошее начало, теперь вы должны знать, как использовать Jest и React Testing Library для написания тестов для ваших реагирующих компонентов, и вы можете начать чтобы изучить другие библиотеки и решения на этой хорошей основе.
Я планирую написать еще одну статью, чтобы охватить больше передовых примеров, если я получу положительный отзыв об этой статье. Еще раз спасибо за ваше время.
Ресурсы, на которые я ссылался при написании этой статьи
- Распространенные ошибки при тестировании на реакцию от Кент К. Доддс
- Исправить предупреждение о неупакованном акте от Kent C. Dodds
- Мой опыт перехода от Enzyme к react-testing-library (Мнение о том, какую библиотеку использовать для React Testing)
- Тестирование библиотечных рецептов (много хороших ресурсов, чтобы узнать больше о библиотеке)
- В голове разработчика - рефакторинг и отладка теста React Йоханнес Кеттманн (я начал изучать React Test с этой статьи, но это гораздо более продвинутый вариант, я напишу об этом позже. )
Особые благодарности ooloo.io и Johannes Kettmann
Тем, кто хочет стать готовым к работе FrontEnd-разработчиком, я бы порекомендовал попробовать курс от ooloo.io. Он знакомит с такими концепциями, как создание идеального дизайна, планирование и реализация сложного компонента пользовательского интерфейса, отладка внутри IDE и Написание интеграционных тестов, которые не требуются в большинстве онлайн-руководств или курсов. И да, я получил много вдохновения от этого курса, который в конечном итоге помог мне написать эту статью.
Эта статья изначально размещена на dev.to