Unit тесты компонентов

Юнит-тестирование компонентов библиотеки MUI (Material-UI) в JavaScript чаще всего выполняется с использованием инструментов Jest и React Testing Library. Основная цель — проверка поведения отдельных компонентов, их рендеринга, взаимодействия с пользователем и корректности пропсов.

Настройка среды

Для тестирования компонентов MUI необходимо установить базовые зависимости:

npm install --save-dev @testing-library/react @testing-library/jest-dom jest

Если используется TypeScript, добавляются типы:

npm install --save-dev @types/jest @testing-library/user-event

Важно учитывать, что многие компоненты MUI зависят от темы и контекста ThemeProvider. Без него тестируемый компонент может рендериться некорректно.

import { ThemeProvider, createTheme } from '@mui/material/styles';

const theme = createTheme();

const renderWithTheme = (ui) => {
  return render(<ThemeProvider theme={theme}>{ui}</ThemeProvider>);
};

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

Для проверки корректного рендеринга компонентов MUI используется render из React Testing Library:

import { render, screen } from '@testing-library/react';
import Button from '@mui/material/Button';

test('кнопка отображается с правильным текстом', () => {
  render(<Button>Нажми меня</Button>);
  const buttonElement = screen.getByRole('button', { name: /Нажми меня/i });
  expect(buttonElement).toBeInTheDocument();
});

Ключевые моменты:

  • Использование role и aria-label для корректного поиска элементов.
  • Проверка наличия в документе через toBeInTheDocument().

Проверка пропсов и состояния компонентов

Многие компоненты MUI принимают пропсы, влияющие на их визуальное состояние:

import Checkbox from '@mui/material/Checkbox';
import userEvent from '@testing-library/user-event';

test('чекбокс может быть отмечен и снят', async () => {
  const user = userEvent.setup();
  render(<Checkbox data-testid="checkbox" />);
  
  const checkbox = screen.getByTestId('checkbox');
  
  expect(checkbox).not.toBeChecked();
  
  await user.click(checkbox);
  expect(checkbox).toBeChecked();
  
  await user.click(checkbox);
  expect(checkbox).not.toBeChecked();
});

Особенности:

  • toBeChecked() и not.toBeChecked() позволяют проверять состояние компонентов, управляемых React.
  • Для симуляции действий пользователя используется userEvent, который более надежен, чем прямые вызовы fireEvent.

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

Компоненты диалогов, модальных окон и всплывающих меню требуют внимания к порталам (Portal) и асинхронным операциям:

import Dialog from '@mui/material/Dialog';
import Button from '@mui/material/Button';

test('диалог открывается по клику', async () => {
  const user = userEvent.setup();
  render(<MyDialogComponent />); // компонент с кнопкой открытия диалога
  
  expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
  
  await user.click(screen.getByText('Открыть диалог'));
  expect(screen.getByRole('dialog')).toBeVisible();
});

Особенности тестирования:

  • Для элементов, рендерящихся в портале, важно использовать screen.getByRole или screen.queryByRole вместо поиска внутри контейнера рендера.
  • Проверка видимости через toBeVisible() более информативна, чем просто присутствие в DOM.

Снимки компонентов (Snapshot Testing)

MUI компоненты имеют сложную структуру DOM, поэтому снимки помогают отслеживать изменения в визуальном отображении:

import { render } from '@testing-library/react';
import Card from '@mui/material/Card';

test('Card рендерится корректно', () => {
  const { asFragment } = render(<Card>Контент карточки</Card>);
  expect(asFragment()).toMatchSnapshot();
});

Примечания:

  • Снимки фиксируют DOM-структуру, включая классы, стили и дочерние элементы.
  • При изменении версии MUI или темы, снимки требуют обновления.

Mock функций и колбэков

Компоненты MUI часто принимают onClick, onChange и другие колбэки. Их проверка осуществляется через jest.fn():

import Button from '@mui/material/Button';

test('колбэк вызывается при клике', async () => {
  const user = userEvent.setup();
  const handleClick = jest.fn();
  
  render(<Button onCl ick={handleClick}>Нажми</Button>);
  
  await user.click(screen.getByText('Нажми'));
  
  expect(handleClick).toHaveBeenCalledTimes(1);
});

Особенности:

  • Проверка количества вызовов с помощью toHaveBeenCalledTimes.
  • Возможна проверка аргументов через toHaveBeenCalledWith.

Тестирование компонентов с асинхронными эффектами

Если компонент MUI использует асинхронные операции (например, загрузка данных для Autocomplete):

import Autocomplete from '@mui/material/Autocomplete';
import TextField from '@mui/material/TextField';

test('Autocomplete отображает варианты после ввода', async () => {
  const user = userEvent.setup();
  const options = ['Apple', 'Banana', 'Cherry'];

  render(
    <Autocomplete
      options={options}
      renderInput={(params) => <TextField {...params} label="Фрукты" />}
    />
  );

  const input = screen.getByLabelText('Фрукты');
  await user.type(input, 'B');

  expect(await screen.findByText('Banana')).toBeInTheDocument();
});

Особенности:

  • Использование findByText для асинхронного ожидания элементов.
  • Симуляция ввода через user.type позволяет проверять фильтрацию и динамическое обновление списка.

Практические рекомендации

  1. Оборачивать компоненты в ThemeProvider для корректного рендеринга MUI стилей.
  2. Использовать screen вместо контейнера — повышает читаемость и совместимость с портальными элементами.
  3. Mocking асинхронных функций через jest или MSW для сетевых запросов.
  4. Сегментировать тесты: проверка рендеринга, состояния, взаимодействий и колбэков отдельно.
  5. Снимки применять осторожно, особенно при частых изменениях DOM компонентов MUI.

Эти подходы позволяют создавать стабильные, поддерживаемые и понятные юнит-тесты для компонентов MUI в современных React-приложениях.