Введение
Тестирование — неотъемлемая часть процесса разработки программного обеспечения, и React — не исключение. В мире React Jest и библиотека тестирования React стали незаменимыми инструментами для создания надежных, удобных в сопровождении и масштабируемых тестов для ваших приложений. В этой статье мы рассмотрим основы тестирования компонентов React с помощью Jest и библиотеки тестирования React, а также предоставим вам знания и лучшие практики, необходимые для обеспечения надежности ваших приложений и отсутствия ошибок.
- Настройка среды тестирования
Прежде чем погрузиться в процесс тестирования, нам нужно настроить нашу тестовую среду. Для этого мы установим Jest, React Testing Library и все необходимые зависимости:
npm install --save-dev jest @testing-library/react @testing-library/jest-dom
Затем создайте файл jest.config.js
в корневом каталоге вашего проекта для настройки Jest:
module.exports = { testEnvironment: 'jsdom', moduleNameMapper: { '\\.(css|less|scss|sass)$': 'identity-obj-proxy', }, setupFilesAfterEnv: ['./jest.setup.js'], };
Создайте файл jest.setup.js
в корневом каталоге вашего проекта, чтобы импортировать необходимые утилиты тестирования:
import '@testing-library/jest-dom';
Теперь ваша тестовая среда настроена и готова к работе!
2. Написание первого теста
Начнем с создания простого компонента React для тестирования. Создайте файл Counter.js
:
import React, { useState } from 'react'; export const Counter = () => { const [count, setCount] = useState(0); const increment = () => setCount(count + 1); const decrement = () => setCount(count - 1); return ( <div> <h1>Counter: {count}</h1> <button onClick={increment}>Increment</button> <button onClick={decrement}>Decrement</button> </div> ); };
Теперь создайте файл Counter.test.js
для написания наших тестов:
import { render, fireEvent } from '@testing-library/react'; import { Counter } from './Counter'; test('renders the counter component with initial state', () => { const { getByText } = render(<Counter />); const counterText = getByText(/Counter: 0/i); expect(counterText).toBeInTheDocument(); }); test('increments the counter value on clicking the increment button', () => { const { getByText } = render(<Counter />); const incrementButton = getByText(/Increment/i); fireEvent.click(incrementButton); const counterText = getByText(/Counter: 1/i); expect(counterText).toBeInTheDocument(); }); test('decrements the counter value on clicking the decrement button', () => { const { getByText } = render(<Counter />); const decrementButton = getByText(/Decrement/i); fireEvent.click(decrementButton); const counterText = getByText(/Counter: -1/i); expect(counterText).toBeInTheDocument(); });
В этом примере мы используем render
для рендеринга нашего компонента Counter
и fireEvent
для имитации взаимодействия с пользователем. Затем мы используем запрос getByText
для поиска элементов в DOM и делаем утверждения, используя функцию Jest expect
.
3. Рекомендации
- Всегда тестируйте взаимодействия с пользователем, а не детали реализации.
- Используйте асинхронные утилиты, такие как запросы
waitFor
иfindBy*
, для компонентов с асинхронным поведением. - Используйте пользовательские функции рендеринга, чтобы обернуть компоненты поставщиками контекста или поставщиками темы.
- Используйте
screen
для запроса элементов вместо методов деструктурирования изrender
.
4. Тестирование асинхронного поведения
Иногда ваши компоненты могут вести себя асинхронно, например получать данные из API. В этих случаях важно использовать асинхронные утилиты для обработки асинхронного характера ваших компонентов. Давайте создадим простой компонент User
, который извлекает пользовательские данные из API:
import React, { useState, useEffect } from 'react'; export const User = ({ userId }) => { const [user, setUser] = useState(null); const [error, setError] = useState(null); useEffect(() => { const fetchUser = async () => { try { const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`); const data = await response.json(); setUser(data); } catch (error) { setError(error.message); } }; fetchUser(); }, [userId]); if (error) return <div>Error: {error}</div>; if (!user) return <div>Loading...</div>; return ( <div> <h1>{user.name}</h1> <p>Email: {user.email}</p> </div> ); };
Теперь давайте напишем тест для этого компонента с помощью асинхронных утилит. Создайте файл User.test.js
:
import { render, screen, waitFor } from '@testing-library/react'; import { User } from './User'; // Mock the API call global.fetch = jest.fn(() => Promise.resolve({ json: () => Promise.resolve({ id: 1, name: 'John Doe', email: '[email protected]', }), }), ); test('fetches and displays user data', async () => { render(<User userId={1} />); expect(screen.getByText('Loading...')).toBeInTheDocument(); await waitFor(() => screen.getByText(/john doe/i)); expect(screen.getByText(/john doe/i)).toBeInTheDocument(); expect(screen.getByText(/john\.doe@example\.com/i)).toBeInTheDocument(); // Ensure the fetch function was called with the correct URL expect(fetch).toHaveBeenCalledWith('https://jsonplaceholder.typicode.com/users/1'); });
В этом примере мы имитируем функцию fetch
для имитации вызова API. Мы используем waitFor
, чтобы дождаться появления элемента в DOM, прежде чем делать утверждения.
5. Пользовательские функции рендеринга
В некоторых случаях вам может понадобиться обернуть ваши компоненты поставщиками контекста или поставщиками темы. Вы можете создать пользовательскую функцию рендеринга для обработки этих сценариев. Создайте файл test-utils.js
:
import { render } from '@testing-library/react'; import { ThemeProvider } from 'styled-components'; import { theme } from './theme'; const customRender = (ui, options) => render(ui, { wrapper: ({ children }) => <ThemeProvider theme={theme}>{children}</ThemeProvider>, ...options }); export * from '@testing-library/react'; export { customRender as render };
Теперь вы можете использовать пользовательскую функцию рендеринга в своих тестах:
import { render, screen } from './test-utils'; import { MyComponent } from './MyComponent'; test('renders the MyComponent', () => { render(<MyComponent />); // Your assertions here });
6. Использование объекта screen
Вместо деструктуризации методов запроса из функции render
рекомендуется использовать объект screen
для запроса элементов. Это приводит к более чистым и читаемым тестам:
import { render, screen } from '@testing-library/react'; import { Counter } from './Counter'; test('increments the counter value on clicking the increment button', () => { render(<Counter />); const incrementButton = screen.getByText(/Increment/i); fireEvent.click(incrementButton); const counterText = screen.getByText(/Counter: 1/i); expect(counterText).toBeInTheDocument(); });
Используя объект screen, вы можете легко увидеть, какие запросы используются в тесте, что упрощает понимание и поддержку.
7. Тестирование пользовательских хуков
Для тестирования пользовательских хуков вы можете использовать пакет `@testing-library/react-hooks`. Сначала установите пакет:
npm install --save-dev @testing-library/react-hooks
Давайте создадим собственный хук с именем useCounter
:
import { useState } from 'react'; export const useCounter = (initialValue = 0) => { const [count, setCount] = useState(initialValue); const increment = () => setCount(count + 1); const decrement = () => setCount(count - 1); return { count, increment, decrement }; };
Теперь создайте файл useCounter.test.js
для проверки нашего пользовательского хука:
import { renderHook, act } from '@testing-library/react-hooks'; import { useCounter } from './useCounter'; test('should increment and decrement the counter value', () => { const { result } = renderHook(() => useCounter(0)); expect(result.current.count).toBe(0); act(() => { result.current.increment(); }); expect(result.current.count).toBe(1); act(() => { result.current.decrement(); }); expect(result.current.count).toBe(0); });
В этом примере мы используем renderHook
для рендеринга нашего пользовательского хука и act
для выполнения действий с хуком. Затем мы делаем утверждения, используя функцию Jest expect
.
Заключение
Освоение тестирования React с помощью Jest и библиотеки тестирования React необходимо для обеспечения надежности, удобства обслуживания и масштабируемости ваших приложений. Следуя рекомендациям и методам, изложенным в этой статье, вы будете на пути к созданию надежного набора тестов для своих компонентов и хуков React. Продолжайте учиться, тестировать и создавать надежные приложения!