Testing стратегия документ

React Testing Library (RTL) — это популярный инструмент для тестирования компонентов React. В отличие от других библиотек, она ориентирована на тестирование пользовательского интерфейса с точки зрения пользователя, а не на внутреннюю структуру компонентов. Это помогает создавать тесты, которые более устойчивы к изменениям в реализации, поскольку они проверяют поведение компонента, а не его внутреннюю логику.

Принципы и цели тестирования

Стратегия тестирования с использованием RTL должна фокусироваться на нескольких ключевых принципах:

  1. Тестирование с точки зрения пользователя RTL стремится к тому, чтобы тесты отражали реальное использование компонентов, а не их внутреннюю реализацию. Это позволяет обеспечить гибкость в случае изменений в коде, не затрагивая тесты.

  2. Минимизация тестов, зависимых от реализации Для поддержания гибкости тестов важно избегать тестирования деталей реализации, таких как структура DOM или внутренние состояния компонентов. Вместо этого следует проверять, как компоненты взаимодействуют с пользователем.

  3. Покрытие всех важных сценариев Важно продумать сценарии использования компонента, включая все возможные состояния, события и взаимодействия. Это включает как положительные, так и отрицательные тесты.

  4. Повторное использование тестов Тесты должны быть независимыми и многоразовыми. Это позволит упростить поддержание тестового покрытия в случае изменения компонентов.

Структура теста

Каждый тест с использованием React Testing Library обычно состоит из трёх основных шагов:

  1. Рендеринг компонента Компонент рендерится в виртуальном DOM, что позволяет протестировать его поведение и взаимодействие с пользователем.

  2. Взаимодействие с компонентом Эмулируются действия пользователя, такие как клики, ввод текста и т. п.

  3. Проверка результатов Проверяется, как компонент реагирует на действия пользователя. Это может включать проверку наличия текста, изменения состояния или появления новых элементов.

Создание тестов с использованием React Testing Library

Рендеринг компонента

Рендеринг компонента с использованием render из библиотеки позволяет создать виртуальный DOM для компонента. Это основной шаг в тестировании, так как только после рендеринга компонента можно выполнить проверку его состояния и поведения.

import { render } from '@testing-library/react';
import MyComponent from './MyComponent';

test('рендер компонента', () => {
  const { getByText } = render(<MyComponent />);
  expect(getByText('Hello World')).toBeInTheDocument();
});

Взаимодействие с компонентом

Эмуляция действий пользователя — это следующий шаг в стратегии тестирования. React Testing Library предоставляет несколько методов для симуляции событий, таких как клики, ввод текста, фокусировка и другие.

import { render, fireEvent } from '@testing-library/react';
import MyComponent from './MyComponent';

test('клик по кнопке изменяет текст', () => {
  const { getByText, getByRole } = render(<MyComponent />);
  const button = getByRole('button');
  fireEvent.click(button);
  expect(getByText('Текст изменился')).toBeInTheDocument();
});

Метод fireEvent позволяет симулировать события на DOM-элементах, а метод getByRole помогает найти элементы, основанные на их роли (например, кнопки или ссылки).

Проверка результатов

После того как компонент был отрендерен и с ним выполнены действия, нужно проверить, как он реагирует. Это можно сделать с помощью различных методов из библиотеки RTL, таких как getByText, getByRole, queryByText и другие.

import { render, fireEvent } from '@testing-library/react';
import MyComponent from './MyComponent';

test('отображение сообщения после клика', () => {
  const { getByText, queryByText } = render(<MyComponent />);
  const button = getByText('Показать сообщение');
  fireEvent.click(button);
  expect(queryByText('Сообщение отображено')).toBeTruthy();
});

queryByText возвращает элемент, если он найден, или null, если его нет. Это позволяет эффективно проверять, отображается ли элемент после выполнения действия.

Использование улучшенных утилит

React Testing Library включает в себя утилиты для упрощения тестирования. Некоторые из них включают:

  • waitFor Этот метод используется для ожидания изменений в компоненте, например, после асинхронных операций (запросы, таймеры и т. д.).

    import { render, fireEvent, waitFor } from '@testing-library/react';
    import MyComponent from './MyComponent';
    
    test('ожидание асинхронного события', async () => {
      const { getByText } = render(<MyComponent />);
      const button = getByText('Загрузить данные');
      fireEvent.click(button);
    
      await waitFor(() => {
        expect(getByText('Данные загружены')).toBeInTheDocument();
      });
    });
  • findBy Утилита findBy работает аналогично getBy, но используется для асинхронных операций. Это удобный способ работать с элементами, которые появляются в DOM через некоторое время.

    import { render, screen } from '@testing-library/react';
    import MyComponent from './MyComponent';
    
    test('поиск элемента через findBy', async () => {
      render(<MyComponent />);
      const element = await screen.findByText('Данные загружены');
      expect(element).toBeInTheDocument();
    });
  • userEvent Библиотека user-event является более высокоуровневым инструментом для симуляции пользовательских событий. Она имитирует реальные действия пользователя, такие как ввод текста, клик и перемещение мыши.

    import { render, screen } from '@testing-library/react';
    import userEvent from '@testing-library/user-event';
    import MyComponent from './MyComponent';
    
    test('ввод текста в поле ввода', () => {
      render(<MyComponent />);
      const input = screen.getByRole('textbox');
      userEvent.type(input, 'Hello, world!');
      expect(input).toHaveValue('Hello, world!');
    });

Сложные тесты и взаимодействие с состоянием

При тестировании более сложных компонентов важно учитывать асинхронные действия, глобальные состояния и эффекты. Использование таких инструментов, как useState и useEffect, требует правильной имитации асинхронных операций и проверки их завершения.

import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import MyComponent from './MyComponent';

test('добавление элемента в список', async () => {
  render(<MyComponent />);
  const input = screen.getByRole('textbox');
  const button = screen.getByRole('button', { name: /добавить/i });

  userEvent.type(input, 'Новый элемент');
  fireEvent.click(button);

  await waitFor(() => {
    expect(screen.getByText('Новый элемент')).toBeInTheDocument();
  });
});

В этом примере выполняются действия по добавлению элемента в список. Важным моментом является использование waitFor для ожидания появления нового элемента в DOM.

Тестирование взаимодействия с сервером

Для имитации сетевых запросов можно использовать такие утилиты, как msw (Mock Service Worker). Это позволяет изолировать тесты от реальных API-запросов и протестировать поведение компонента, реагирующего на успешные или неуспешные ответы от сервера.

import { render, screen, waitFor } from '@testing-library/react';
import MyComponent from './MyComponent';
import { server, rest } from 'msw';
import { setupServer } from 'msw/node';

// Настройка mock-сервера
const server = setupServer(
  rest.get('/api/data', (req, res, ctx) => {
    return res(ctx.json({ message: 'Данные загружены' }));
  })
);

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

test('загрузка данных с сервера', async () => {
  render(<MyComponent />);
  await waitFor(() => screen.getByText('Данные загружены'));
  expect(screen.getByText('Данные загружены')).toBeInTheDocument();
});

Этот тест показывает, как можно мокировать серверные ответы для тестирования компонента без обращения к реальному API.

Заключение

Реализация стратегии тестирования с React Testing Library требует внимательного подхода к организации тестов и правильной эмуляции поведения компонентов. Важно придерживаться принципов тестирования с точки зрения пользователя, минимизируя зависимость от реализации компонентов. Правильное использование утилит для асинхронных операций и эмуляции пользовательских событий позволит создать надежные тесты, которые обеспечат стабильную работу приложения и облегчат его поддержку в будущем.