Тестирование рендеринга

Тестирование рендеринга — проверка того, что React (или другой UI-фреймворк) компонент отображает нужный контент при заданных props и state, через render из Testing Library или snapshot-тесты.

Зачем нужно

Компонент должен рендерить правильный контент при разных наборах props: показывать данные пользователя, скрывать элементы при отсутствии прав, отображать loading-состояние. Тестирование рендеринга предотвращает регрессии в отображении при изменении логики.

Где используется

  • Тестирование условного рендеринга (if/else, тернарные операторы)
  • Проверка рендеринга списков с разным количеством элементов
  • Тест loading/error/empty состояний компонента
  • Проверка корректности отображения данных из props

Основной контент

Базовый тест рендеринга

// UserCard.test.tsx
import { render, screen } from '@testing-library/react';
import { UserCard } from './UserCard';

test('отображает имя и email пользователя', () => {
  render(<UserCard name="Alice" email="alice@example.com" />);

  expect(screen.getByText('Alice')).toBeInTheDocument;
  expect(screen.getByText('alice@example.com')).toBeInTheDocument;
});

Условный рендеринг

// Badge.tsx — рендерит badge только для admin
test('показывает badge для admin', () => {
  render(<UserCard name="Alice" role="admin" />);
  expect(screen.getByText('Admin')).toBeInTheDocument;
});

test('скрывает badge для обычного пользователя', () => {
  render(<UserCard name="Bob" role="user" />);
  expect(screen.queryByText('Admin')).not.toBeInTheDocument;
});

Loading/Error/Empty состояния

// DataList.test.tsx
test('показывает спиннер при загрузке', () => {
  render(<DataList status="loading" data={} />);
  expect(screen.getByRole('progressbar')).toBeInTheDocument;
  expect(screen.queryByRole('list')).not.toBeInTheDocument;
});

test('показывает ошибку при сбое', () => {
  render(<DataList status="error" data={} error="Ошибка сети" />);
  expect(screen.getByText('Ошибка сети')).toBeInTheDocument;
});

test('показывает пустое состояние при отсутствии данных', () => {
  render(<DataList status="success" data={} />);
  expect(screen.getByText('Данных нет')).toBeInTheDocument;
  expect(screen.queryByRole('listitem')).not.toBeInTheDocument;
});

test('рендерит список при наличии данных', () => {
  const data = [{ id: 1, name: 'Item 1' }, { id: 2, name: 'Item 2' }];
  render(<DataList status="success" data={data} />);
  expect(screen.getAllByRole('listitem')).toHaveLength(2);
  expect(screen.getByText('Item 1')).toBeInTheDocument;
});

Рендеринг с провайдерами

// Если компонент использует Context, Redux, Router — нужна обёртка
import { renderWithProviders } from '../test-utils';

test('UserProfile рендерится с store', () => {
  renderWithProviders(<UserProfile userId={1} />, {
    preloadedState: {
      users: { '1': { name: 'Alice', email: 'alice@test.com' } },
    },
  });

  expect(screen.getByText('Alice')).toBeInTheDocument;
});

// test-utils.tsx
export function renderWithProviders(ui: React.ReactElement, options = {}) {
  const { preloadedState, ...renderOptions } = options;
  const store = configureStore({ reducer: rootReducer, preloadedState });
  return render(<Provider store={store}>{ui}</Provider>, renderOptions);
}

Snapshot — для фиксации структуры

test('Button рендерится стабильно', () => {
  const { container } = render(<Button variant="primary" label="Click" />);
  expect(container).toMatchSnapshot;
});

Частые ошибки

  • Тест HTML-структуры вместо пользовательского контентаexpect(container.querySelector('.btn-primary')).toBeTruthy ломается при рефакторинге стилей; проверяй через getByRole, getByText
  • getBy для проверки отсутствияexpect( => screen.getByText('Admin')).toThrow неудобно; используй queryByText с .not.toBeInTheDocument
  • Snapshot без review — принятый снимок с багом в рендеринге закрепляет баг; всегда проверяй diff снимков

Связанные темы

Ресурсы