Введение
Модульные тесты необходимы для обеспечения уверенности в написанном нами коде. В четвертой части этой серии я расскажу о том, как написать наш первый модульный тест, и о том, как я решил, что тестировать.
"Первая часть"
"Вторая часть"
"Третья часть"
Модульный тест – это метод тестирования изолированного и отдельного фрагмента кода, модуля. В контексте React это, скорее всего, относится к тестированию наших компонентов изолированно и любой связанной с ними функции
Замечательно! Но как мы решаем, что именно тестировать? Наши тесты должны касаться только тех взаимодействий, которых ожидает пользователь. Детали реализации, такие как имена переменных, имена функций и т. д., не должны иметь значения в наших тестах.
Решение о модульном тесте
Чтобы дать краткий обзор, мы создали очень простое приложение, которое извлекает некоторый общий JSON и отображает его на странице:
Что, как мы можем разумно предположить, заинтересует пользователя:
- При нажатии на кнопку получения сообщений он должен перечислить сообщения.
- При нажатии на кнопку очистки сообщений он должен очистить сообщения.
Что нас не волнует:
- Имя функции, вызывающей запрос на выборку
- Имена классов элементов в содержимом
Глядя на наш код
Давайте сравним это, взглянув на наш код.
<section className="App-buttons"> <button onClick={fetchPosts} type="button"> Fetch Posts </button> <button onClick={clearPosts} type="button"> Clear posts </button> </section> {loading && <p>Loading...</p>} {posts.map((post) => ( <article key={post.id}> <h3>{post.title}</h3> <p>{post.body}</p> </article> ))} </main>
Итак, чтобы визуализировать это, как если бы мы были конечным пользователем:
Что здесь не так? Что ж, мы тестируем некоторые детали реализации, такие как содержание ответа и то, была ли функция выполнена.
Эти части должны стать черным ящиком для наших тестов.
Что-то лучше было бы:
Вы можете спросить, а разве первый вариант не проверяет больше?
Это неправильный подход к этому. Код меняется, мы склонны к рефакторингу. Если наши тесты постоянно ломаются при внесении изменений в код, мы добавляем много накладных расходов на разработку.
В конце концов, нам нужно, чтобы сообщения извлекались и отображались. Детали функции не имеют значения.
Изменение нашего приложения для первого модульного теста
Давайте изменим наш файл vite.config.js
:
import react from '@vitejs/plugin-react'; // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], test: { globals: true, environment: 'jsdom', }, });
Написание модульного теста
Давайте продолжим и создадим наш первый тест. В корне нашего проекта создадим App.test.js
import { describe } from 'vitest'; describe('Testing our React application', () => { it('Fetch posts', () => {}); });
Но ждать! Создавая наш тест, давайте посмотрим, пройдены они или нет. В терминале выполните следующую команду:
vitest --watch
Это должно привести к ошибке, поскольку в нашем тесте нет утверждений:
Далее, чтобы отрендерить наши компоненты, нам понадобится помощь еще одной библиотеки: React-testing-library.
Семейство пакетов @testing-library помогает тестировать компоненты пользовательского интерфейса с учетом интересов пользователя.
npm install @testing-library/react @testing-library/jest-dom @testing-library/user-events --save-dev
Во-первых, давайте просто проверим, что мы можем правильно отрендерить и пройти тест:
import React from 'react'; import { describe, expect, it } from 'vitest'; import { render, screen } from '@testing-library/react'; import '@testing-library/jest-dom'; import App from './App'; describe('Testing our React application', () => { it('Fetch posts', async () => { render(<App />); expect(screen.getByText(/Modern React Testing/i)).toBeInTheDocument(); }); });
Здесь мы просто визуализируем наше приложение и проверяем заголовок нашего заголовка. Ожидание в данном случае — это наше утверждение, которое решает, пройдём мы тест или нет.
Модульный тест для получения
Но это не имеет отношения к реальному тестированию. Итак, давайте попробуем поработать с нашей кнопкой и функциями поствыборки.
import React from 'react'; import { describe } from 'vitest'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import '@testing-library/jest-dom'; import App from './App'; describe('Testing our React application', () => { it('Fetch posts', () => { const user = userEvent.setup(); render(<App />); expect(screen.getByText(/Modern React Testing/i)).toBeInTheDocument(); }); });
userEvent
в данном случае позволяет нам следовать основному принципу, который мы изложили в самом начале: создавайте тесты, которые могут максимально точно походить на то, как пользователь взаимодействует с приложением.
Например, внутри нашего объекта userEvent у нас есть доступ к функции click! И с помощью этой функции щелчка мы можем отправить аргумент для поиска нашей кнопки.
userEvent.click(screen.getByRole('button', { name: 'Fetch Posts'}));
Поясним это на схеме:
Множество утилит для нажатия кнопки в нашем модульном тесте. Однако функция, вызываемая нажатием кнопки, является асинхронной. Итак, давайте сделаем наш тест асинхронным и дождемся загрузки сообщений.
Позже мы смоделируем этот запрос, чтобы проверить дополнительные возможности.
import { describe } from 'vitest'; import { render, screen } from '@testing-library/react'; import { userEvent } from '@testing-library/user-event'; import App from './App'; describe('Testing our React application', async () => { it('Fetch posts', () => { render(<App />); userEvent.click(screen.getByRole('button', { name:'Fetch Posts'})); await waitForElementToBeRemoved(() => screen.queryByLabelText('Loading...') ); expect(screen.getByRole('heading', { level: 3 })).toBeDefined(); }); });
Идеальный. И снова мы используем функциональность экрана и просто ждем, пока исчезнет текст «Загрузка».
НО, это выдаст нам ошибку… но почему? Что ж, мы не заглушаем и не издеваемся над сервисом выборки, который вызывается при нажатии кнопки. Как мы можем это решить?
Завершение
В следующей статье я расскажу, как использовать MSW для имитации запросов, запускаемых из тестов. Мы рассмотрим настройку и интегрируем ее с этим тестом!
Больше контента на Relatable Code
Давайте подключим
Если вам это понравилось, не стесняйтесь связаться со мной в LinkedIn или Twitter.
Ознакомьтесь с моей дорожной картой бесплатного разработчика и еженедельными новостями технологической отрасли в моем рассылках.