Моки и stubs

В экосистеме Radix UI моки и stubs играют ключевую роль при разработке интерактивных компонентов, особенно в сочетании с фреймворками типа React. Они позволяют изолировать поведение компонента, эмулировать внешние зависимости и контролировать сценарии взаимодействия без реальной реализации логики или API-запросов.


Отличие мока от стаба

  • Stub — это объект или функция, которая заменяет часть кода, возвращая заранее определённые значения. Используется для контроля результатов и упрощения тестов. Пример: stub функции, которая возвращает фиксированные данные для выпадающего меню Radix DropdownMenu:

    const fetchItemsStub = jest.fn(() => [
      { id: 1, label: 'Элемент 1' },
      { id: 2, label: 'Элемент 2' },
    ]);

    Здесь stub гарантирует, что компонент всегда получит одинаковый набор данных.

  • Mock — объект, который не только заменяет зависимость, но и позволяет отслеживать взаимодействие с ней: сколько раз вызвана функция, с какими аргументами, какие методы были вызваны. Пример: mock для проверки вызова обработчика выбора элемента в Select компоненте Radix:

    const onSelectM ock = jest.fn();
    render(<Sel ect onValueCha nge={onSelectMock} />);
    
    userEvent.click(screen.getByText('Элемент 1'));
    expect(onSelectMock).toHaveBeenCalledWith('Элемент 1');

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

  1. Изоляция внешних зависимостей Radix UI предоставляет низкоуровневые primitives, такие как Popover, Dialog, Slider, которые часто зависят от данных или состояний приложения. Stubs позволяют избежать фактического вызова API и гарантируют предсказуемость тестов.

  2. Эмуляция пользовательского взаимодействия Моки используются для отслеживания событий. Например, при тестировании Tabs можно проверить, что при переключении вкладки вызываются правильные коллбеки:

    const onTabChangeM ock = jest.fn();
    render(
      <Tabs onValueCha nge={onTabChangeMock}>
        <TabsList>
          <TabsTrigger value="tab1">Вкладка 1</TabsTrigger>
          <TabsTrigger value="tab2">Вкладка 2</TabsTrigger>
        </TabsList>
        <TabsContent value="tab1">Содержимое 1</TabsContent>
        <TabsContent value="tab2">Содержимое 2</TabsContent>
      </Tabs>
    );
    
    userEvent.click(screen.getByText('Вкладка 2'));
    expect(onTabChangeMock).toHaveBeenCalledWith('tab2');
  3. Тестирование асинхронных операций Для компонентов Radix UI, которые зависят от асинхронных данных (например, фильтры в DropdownMenu), stubs позволяют контролировать поведение без реального запроса:

    const fetchDataStub = jest.fn(() => Promise.resolve(['Опция 1', 'Опция 2']));
    await act(async () => {
      render(<MyDropdown fetchItems={fetchDataStub} />);
    });
    expect(fetchDataStub).toHaveBeenCalled();
    expect(screen.getByText('Опция 1')).toBeInTheDocument();

Важные практики при использовании моков и стабов

  • Явное определение ожиданий: каждый мок должен иметь чётко заданное поведение и проверку вызовов.
  • Изоляция: не следует использовать реальные реализации функций или API в тестах компонентов UI.
  • Повторное использование: stubs, возвращающие одинаковые данные, можно использовать в нескольких тестах, обеспечивая консистентность.
  • Чистота теста: после выполнения каждого теста сбрасывать вызовы моков (mockReset или mockClear), чтобы избежать взаимного влияния тестов.

Интеграция с инструментами

  • Jest — стандарт для создания моков и стабов в React-проектах с Radix UI.
  • React Testing Library — позволяет тестировать взаимодействие компонентов, а моки обеспечивают контроль над коллбеками и данными.
  • MSW (Mock Service Worker) — для мокирования сетевых запросов, если Radix UI компонент зависит от данных с сервера.

Пример комбинированного подхода: использование stubs для данных и mock функций для проверки событий:

import { setupServer } fr om 'msw/node';
import { rest } from 'msw';

const server = setupServer(
  rest.get('/api/items', (req, res, ctx) => res(ctx.json(['Опция A', 'Опция B'])))
);

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

const onChangeM ock = jest.fn();

render(<Dropdown fetchItems={() => fetch('/api/items')} onCha nge={onChangeMock} />);
userEvent.click(screen.getByText('Опция A'));
expect(onChangeMock).toHaveBeenCalledWith('Опция A');

Резюме по использованию

  • Stubs гарантируют предсказуемость данных и состояния компонента.
  • Mocks позволяют проверить взаимодействия и коллбеки.
  • Правильная комбинация stubs и mocks обеспечивает стабильность, изоляцию и точность тестов Radix UI компонентов.
  • Важно следить за чистотой моков, сбрасывать их состояние после тестов и избегать реальных зависимостей внутри unit-тестов.

Такой подход позволяет создавать надёжные тесты для Radix UI primitives, поддерживая высокое качество интерактивных интерфейсов и предотвращая регрессии при развитии проекта.