Мы уже знаем, как измерять его и как делать наши тесты быстрее. Но иногда бывают такие случаи, когда стандартных знаний недостаточно. В этой лекции мы разберём сложные кейсы тестирования, типичные проблемы и, главное, как с ними справляться.
1. Тестирование компонентов с контекстом (React Context)
Контекст в React — отличный способ передавать состояние между компонентами без пропсов. Но когда дело доходит до тестирования, возникает вопрос: "Как протестировать компоненты, которые зависят от контекста?"
Пример сложного случая:
У нас есть глобальный контекст авторизации, который предоставляет информацию о пользователе и функцию logout. Как протестировать компонент, использующий этот контекст?
// AuthContext.tsx
import React, { createContext, useContext } from 'react';
interface AuthContextType {
user: string | null;
logout: () => void;
}
const AuthContext = createContext<AuthContextType | undefined>(undefined);
export const AuthProvider: React.FC = ({ children }) => {
const [user, setUser] = React.useState<string | null>('JohnDoe');
const logout = () => setUser(null);
return (
<AuthContext.Provider value={{ user, logout }}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
};
И сам компонент:
// UserProfile.tsx
import React from 'react';
import { useAuth } from './AuthContext';
export const UserProfile: React.FC = () => {
const { user, logout } = useAuth();
return (
<div>
<p>Welcome, {user || 'Guest'}!</p>
{user && <button onClick={logout}>Logout</button>}
</div>
);
};
Тест:
Для тестирования контекста мы можем создать его провайдер с фиктивными данными прямо в наших тестах.
// UserProfile.test.tsx
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import { UserProfile } from './UserProfile';
import { AuthContext } from './AuthContext';
test('renders UserProfile with user and handles logout', () => {
const mockLogout = jest.fn();
render(
<AuthContext.Provider value={{ user: 'TestUser', logout: mockLogout }}>
<UserProfile />
</AuthContext.Provider>
);
// Проверяем рендер компонента
expect(screen.getByText(/Welcome, TestUser/i)).toBeInTheDocument();
const logoutButton = screen.getByRole('button', { name: /logout/i });
expect(logoutButton).toBeInTheDocument();
// Симулируем нажатие на кнопку "Logout"
fireEvent.click(logoutButton);
expect(mockLogout).toHaveBeenCalledTimes(1);
});
Главная мысль:
Для контекста создавайте мок-провайдеры с фиктивными данными. Это позволит изолировать тестируемый компонент и не зависеть от глобального состояния приложения.
2. Тестирование асинхронных операций с задержками
Асинхронные операции, такие как запросы к API, часто сопровождаются задержками. Проблема возникает, когда наши тесты начинают "опережать" реальное выполнение кода.
Пример сложного случая:
Компонент загружает данные при монтировании и отображает индикатор загрузки.
// AsyncComponent.tsx
import React, { useEffect, useState } from 'react';
export const AsyncComponent: React.FC = () => {
const [data, setData] = useState<string | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async () => {
setLoading(true);
await new Promise((resolve) => setTimeout(resolve, 1000)); // симуляция задержки
setData('Fetched Data');
setLoading(false);
};
fetchData();
}, []);
if (loading) {
return <p>Loading...</p>;
}
return <p>{data}</p>;
};
Тест:
Для таких случаев используйте waitFor из React Testing Library.
import React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import { AsyncComponent } from './AsyncComponent';
test('displays loading indicator and then fetched data', async () => {
render(<AsyncComponent />);
// Проверяем отображение индикатора загрузки
expect(screen.getByText(/loading/i)).toBeInTheDocument();
// Ждём, пока текст "Fetched Data" появится в DOM
await waitFor(() =>expect(screen.getByText(/Fetched Data/i)).toBeInTheDocument());
});
Главная мысль:
Асинхронные операции требуют waitFor или findBy для проверки элементов, которые появляются позже.
3. Тестирование сложных пользовательских взаимодействий
Сложные компоненты с множеством взаимодействий, например, модальные окна или выпадающие списки, могут быть вызовом для тестирования.
Пример сложного случая:
Компонент открывает модальное окно по клику.
// ModalComponent.tsx
import React, { useState } from 'react';
export const ModalComponent: React.FC = () => {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<button onClick={() => setIsOpen(true)}>Open Modal</button>
{isOpen && (
<div role="dialog">
<p>Modal Content</p>
<button onClick={() => setIsOpen(false)}>Close</button>
</div>
)}
</div>
);
};
Тест:
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import { ModalComponent } from './ModalComponent';
test('opens and closes modal', () => {
render(<ModalComponent />);
// Проверяем, что модальное окно изначально скрыто
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
// Открываем модальное окно
fireEvent.click(screen.getByText(/open modal/i));
expect(screen.getByRole('dialog')).toBeInTheDocument();
// Закрываем модальное окно
fireEvent.click(screen.getByText(/close/i));
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
});
Главная мысль:
Для проверки сложных взаимодействий последовательно симулируйте действия пользователя и проверяйте состояние интерфейса на каждом шаге.
Типичные проблемы и как их избегать
Избыточные тесты. Не нужно повторять тестирование одной и той же логики на всех уровнях. Например, протестировали API-запросы отдельно — не тестируйте их снова в рендер-тестах.
Зависимость от внешних данных. Мокайте запросы к API. Используйте библиотеки вроде
mswдля более реалистичного тестирования.Сложные мок-данные. Всегда упрощайте моковые данные до минимально необходимого для теста. Чем проще фикстура — тем меньше вероятность ошибок.
Игнорирование синхронности. Не забывайте использовать
waitForилиfindBy, когда работаете с асинхронными компонентами.
Итак, тестирование React-компонентов может быть сложным, но с правильными инструментами и подходами — это становится вполне выполнимой задачей. Тесты помогают не только обнаруживать ошибки, но и обеспечивают уверенность в стабильности приложения. Пишите тесты, и пусть ваши баги исчезают быстрее, чем типизация ломает код начинающего разработчика!
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ