Паттерн Render Props позволяет компонентам делиться функциональностью через функцию, которая возвращает элементы JSX. Это особенно полезно для создания гибких компонентов, которые могут принимать различные логики и состояния. Тестирование таких компонентов требует особого подхода, поскольку мы имеем дело с компонентами, которые не напрямую рендерят элементы, а передают функциональность через функцию. В этой статье будет рассмотрено, как тестировать компоненты, использующие паттерн Render Props, с помощью React Testing Library.
Компонент, использующий Render Props, предоставляет функцию, которая передается в качестве дочернего элемента. Эта функция, в свою очередь, может использовать состояние и логику компонента и возвращать JSX. Пример:
const DataFetcher = ({ render }) => {
const [data, setData] = React.useState(null);
React.useEffect(() => {
fetch('/api/data')
.then((response) => response.json())
.then((data) => setData(data));
}, []);
return render(data);
};
Здесь компонент DataFetcher использует проп
render, который принимает функцию для рендеринга данных.
Это позволяет пользователю компонента гибко управлять выводом в
зависимости от полученных данных.
При тестировании компонента с Render Props важно понимать, что необходимо проверить не только сам процесс передачи данных через функцию, но и то, как этот компонент взаимодействует с состоянием, асинхронными операциями и пользовательскими событиями.
Первым шагом является проверка, что компонент рендерит правильный
JSX, используя переданную функцию. Для этого можно подать в
render проп функцию, которая возвращает элемент, и
убедиться, что он рендерится корректно.
import { render, screen } from '@testing-library/react';
test('DataFetcher renders data correctly', () => {
const mockRender = (data) => <div>{data ? data : 'Loading...'}</div>;
render(<DataFetcher render={mockRender} />);
expect(screen.getByText('Loading...')).toBeInTheDocument();
});
В данном тесте мы передаем компоненту DataFetcher
функцию mockRender, которая отображает “Loading…” до того,
как данные будут загружены.
Так как компонент может работать с асинхронными запросами, важно протестировать его поведение при получении данных. Для этого можно использовать асинхронные ожидания, чтобы проверить, что компонент рендерит правильные данные после завершения асинхронного запроса.
import { render, screen, waitFor } from '@testing-library/react';
test('DataFetcher loads and renders data', async () => {
const mockData = { id: 1, name: 'Test' };
const mockRender = (data) => <div>{data ? data.name : 'Loading...'}</div>;
// Мокаем асинхронную загрузку данных
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve(mockData),
})
);
render(<DataFetcher render={mockRender} />);
expect(screen.getByText('Loading...')).toBeInTheDocument();
// Ожидаем, что данные загрузятся и отобразятся
await waitFor(() => expect(screen.getByText(mockData.name)).toBeInTheDocument());
});
В данном примере мы используем jest.fn() для мока
асинхронного вызова fetch. Мы проверяем, что вначале
отображается “Loading…”, а после завершения загрузки данных компонент
рендерит правильное имя.
Если компонент с Render Props имеет внутреннюю логику для обновления данных, можно протестировать, как он реагирует на изменения состояния или взаимодействие с пользователем.
import { render, screen, fireEvent } from '@testing-library/react';
test('DataFetcher updates data on button click', async () => {
const mockData1 = { id: 1, name: 'Test1' };
const mockData2 = { id: 2, name: 'Test2' };
const mockRender = (data, updateData) => (
<div>
<div>{data ? data.name : 'Loading...'}</div>
<button onCl ick={() => updateData(mockData2)}>Change Data</button>
</div>
);
const mockUpdateData = jest.fn();
render(<DataFetcher render={(data) => mockRender(data, mockUpdateData)} />);
fireEvent.click(screen.getByText('Change Data'));
expect(mockUpdateData).toHaveBeenCalledWith(mockData2);
});
Здесь компонент рендерит кнопку, которая при нажатии обновляет данные
с помощью функции updateData. Мы проверяем, что при клике
вызывается функция обновления данных, и передаются правильные
значения.
Для более сложных взаимодействий, например, при вводе текста в поле,
можно использовать библиотеку @testing-library/user-event,
которая имитирует действия пользователя более реалистично, чем
fireEvent.
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
test('User input triggers render update', async () => {
const mockRender = (data, updateData) => (
<div>
<input
type="text"
value={data ? data.name : ''}
onCha nge={(e) => updateData({ name: e.target.value })}
/>
<div>{data ? data.name : 'No Data'}</div>
</div>
);
const mockData = { name: 'Initial' };
const mockUpdateData = jest.fn();
render(<DataFetcher render={(data) => mockRender(data, mockUpdateData)} />);
userEvent.type(screen.getByRole('textbox'), 'Updated Name');
expect(mockUpdateData).toHaveBeenCalledWith({ name: 'Updated Name' });
});
Этот тест проверяет, как компонент обновляет данные при вводе текста
в поле ввода. Мы используем userEvent.type для имитации
ввода пользователя.
Тестирование компонентов с Render Props требует внимания к деталям, особенно когда речь идет об асинхронных данных и взаимодействиях. С использованием React Testing Library можно легко протестировать такие компоненты, проверяя, что они рендерят правильные данные, обрабатывают асинхронные запросы и корректно взаимодействуют с пользователем. Тесты должны покрывать основные случаи использования компонента, включая загрузку данных, обновление состояния и взаимодействия с пользователем.