Когда тесты становятся более сложными, их код может начать терять читаемость, а также трудности с поддержанием растут. Для улучшения качества тестов и повышения их удобства чтения и поддержки необходим рефакторинг тестов. В контексте работы с React Testing Library это особенно актуально, так как библиотеки и компоненты с каждым новым проектом становятся более многообразными.
Если в нескольких тестах требуется одинаковая настройка компонента или выполнение общих действий (например, отрисовка компонента с определёнными пропсами), лучше вынести это в отдельные функции или моки. Это повысит читаемость и уменьшит количество повторяющегося кода.
Пример:
const renderWithProviders = (ui, { providerProps, ...renderOptions } = {}) => {
return render(
<SomeContext.Provider value={providerProps}>
{ui}
</SomeContext.Provider>,
renderOptions
);
};
test('should render with context', () => {
renderWithProviders(<Component />);
expect(screen.getByText('Some Text')).toBeInTheDocument();
});
beforeEach и afterEachДля настройки и очистки данных можно использовать хуки
beforeEach и afterEach. Это позволяет избежать
дублирования кода, а также гарантирует, что каждый тест будет изолирован
от других.
Пример:
let mockFunction;
beforeEach(() => {
mockFunction = jest.fn();
});
test('should call mock function', () => {
mockFunction();
expect(mockFunction).toHaveBeenCalledTimes(1);
});
При работе с React Testing Library рекомендуется писать вспомогательные функции для взаимодействия с элементами UI. Это улучшает читаемость тестов и делает код более декларативным.
Пример:
const getButton = () => screen.getByRole('button', { name: /submit/i });
test('should call submit on button click', () => {
render(<Form />);
fireEvent.click(getButton());
expect(handleSubmit).toHaveBeenCalled();
});
Асинхронные тесты часто становятся сложными и трудными для чтения,
если в них не соблюдается порядок выполнения операций. Использование
findBy* методов, которые автоматически ожидают появления
элементов, помогает упростить код.
Пример:
test('should render data after async fetch', async () => {
render(<AsyncComponent />);
const dataElement = await screen.findByText(/loaded data/i);
expect(dataElement).toBeInTheDocument();
});
Кроме того, важно избегать использования wait и других
низкоуровневых решений, если можно обойтись встроенными методами
ожидания. Это делает тесты более стабильными и предсказуемыми.
Часто для того, чтобы тестировать события, требуется писать длинный код для имитации действий пользователя. Чтобы сделать тесты короче, можно использовать пользовательские функции для симуляции этих событий.
Пример:
const user = userEvent.setup();
test('should add item to cart', async () => {
render(<Cart />);
await user.click(screen.getByRole('button', { name: /add to cart/i }));
expect(screen.getByText('Item added to cart')).toBeInTheDocument();
});
Иногда тесты могут содержать сложные ассерты, которые можно упростить, разбив их на несколько шагов. Это поможет сделать тесты более понятными и предотвратит ошибки в будущем.
Пример:
test('should display error message if input is empty', () => {
render(<Form />);
fireEvent.change(screen.getByLabelText(/name/i), { target: { value: '' } });
fireEvent.click(screen.getByText(/submit/i));
expect(screen.getByText(/name is required/i)).toBeInTheDocument();
});
Разбиение сложных ассертов на несколько более простых позволяет легче отслеживать, на каком этапе возникает ошибка.
Иногда бывает полезно уменьшить количество тестов, оставив только те, которые проверяют критически важные моменты. Удаление лишних тестов помогает снизить время выполнения тестов и уменьшить их сложность.
Рефакторинг тестов не должен быть редким или случайным. В идеале, его следует выполнять регулярно. Применение принципов рефакторинга в процессе разработки тестов помогает поддерживать чистоту и удобство кода. Важно понимать, что рефакторинг не должен изменять поведение тестов; его цель — улучшить структуру и читаемость.
При рефакторинге стоит также следить за тем, чтобы тесты оставались независимыми. Изменение одного теста не должно влиять на выполнение других, и каждый тест должен быть самодостаточным.
Рефакторинг тестов — это не просто изменение структуры, но и постоянное стремление к улучшению качества и упрощению работы с тестами. Это важная часть работы с тестированием, которая позволяет не только повысить стабильность кода, но и ускорить процесс разработки, уменьшая количество багов и повышая доверие к тестам.