Мокирование компонентов

Мокирование — важный элемент в процессе тестирования, позволяющий изолировать тестируемый код от внешних зависимостей. В контексте тестирования с использованием Jest мокирование компонентов становится неотъемлемой частью подхода к созданию эффективных и независимых тестов. В этой главе будет рассмотрено, как правильно использовать мокирование для тестирования компонентов в JavaScript с использованием Jest.

Зачем использовать мокирование?

В процессе разработки часто встречаются ситуации, когда компонент зависит от внешних сервисов, API, или других модулей. Тестирование такого компонента без мокирования приведет к высокой вероятности ошибок, нестабильности тестов и излишней зависимости от внешних систем. Мокирование позволяет создать подмену этих зависимостей, что делает тесты более предсказуемыми, быстрыми и независимыми от реальных внешних сервисов.

Основы мокирования в Jest

Jest предоставляет несколько методов для мокирования различных типов зависимостей:

  1. jest.fn() — для создания фиктивных функций (мок-функций).
  2. jest.mock() — для замены модулей или компонентов.
  3. jest.spyOn() — для шпионских функций, которые отслеживают вызовы методов объекта.

Мок-функции с jest.fn()

Функция jest.fn() позволяет создать мок-функцию, которую можно использовать для проверки, был ли вызван тот или иной метод с нужными аргументами. Например, если у нас есть функция, которая вызывает метод компонента:

const myComponent = {
  fetchData: () => 'data',
};

const mockFetchData = jest.fn(() => 'mocked data');

// Пример использования
myComponent.fetchData = mockFetchData;

Теперь, вместо оригинальной функции, будет вызываться мок-функция, и можно проверить, сколько раз и с какими аргументами она была вызвана:

test('fetchData is called', () => {
  myComponent.fetchData();
  expect(mockFetchData).toHaveBeenCalled();
});

Мокирование модулей с jest.mock()

Для более сложных зависимостей, таких как модули или библиотеки, используется метод jest.mock(). Например, если компонент зависит от библиотеки для работы с API, мы можем замокировать этот модуль, чтобы избежать реальных запросов в процессе тестирования:

// Модуль для API
import api from './api';

// Мокирование модуля api
jest.mock('./api', () => ({
  fetchData: jest.fn(() => Promise.resolve({ data: 'mocked data' })),
}));

// Тест компонента, использующего этот API
test('fetches data from api', async () => {
  const data = await api.fetchData();
  expect(data).toEqual({ data: 'mocked data' });
});

Здесь функция jest.mock() позволяет заменить реальный вызов API на мокированную версию, которая возвращает заранее подготовленные данные. Это исключает возможность случайных внешних зависимостей в процессе тестирования.

Мокирование методов объектов с jest.spyOn()

Иногда требуется шпионить за вызовами методов уже существующих объектов или классов. В этом случае используется метод jest.spyOn(). Он позволяет отслеживать вызовы и аргументы методов, не изменяя их поведение:

const myObject = {
  doSomething: (value) => value * 2,
};

test('spying on doSomething method', () => {
  const spy = jest.spyOn(myObject, 'doSomething');

  myObject.doSomething(3);

  expect(spy).toHaveBeenCalledWith(3);
  spy.mockRestore();  // Восстановление оригинального метода
});

Метод jest.spyOn() не только шпионит за вызовами, но и позволяет замокировать поведение метода с помощью mockReturnValue или mockImplementation.

Мокирование компонентов React

При тестировании React-компонентов часто возникает необходимость в замещении внутренних зависимостей, таких как функции, API-запросы или даже другие компоненты. Jest предоставляет возможности для мокирования таких зависимостей, используя как описанные выше методы, так и специальные утилиты, такие как jest.mock() для замены компонент.

Мокирование дочерних компонентов

Если ваш компонент зависит от других компонентов, которые не влияют на логику тестируемого компонента, вы можете замокировать эти дочерние компоненты:

// Компонент Button, который будет замокирован
jest.mock('./Button', () => 'Button');

test('renders without crashing', () => {
  const { container } = render(<MyComponent />);
  expect(container).toMatchSnapshot();
});

В этом примере компонент Button заменяется строкой 'Button', что исключает его влияние на тестирование компонента MyComponent.

Мокирование API-запросов в компонентах

Мокирование API-запросов в компонентах может быть выполнено с помощью библиотеки jest-fetch-mock или стандартного мокирования с jest.mock(). В случае использования стандартного мокирования можно замокировать функции для имитации запросов:

import { fetchData } from './api';

// Мокирование fetch
global.fetch = jest.fn(() =>
  Promise.resolve({
    json: () => Promise.resolve({ data: 'mocked data' }),
  })
);

test('fetches data from API', async () => {
  const data = await fetchData();
  expect(data).toEqual({ data: 'mocked data' });
});

Здесь мы замещаем глобальную функцию fetch, которая обычно используется для отправки запросов, на мокированную версию, возвращающую заранее подготовленные данные.

Использование моков с асинхронными функциями

Для асинхронных операций важно помнить, что мок-функции и мокированные методы также поддерживают асинхронную работу. Мок-функции могут возвращать промисы, что позволяет правильно тестировать асинхронные компоненты и их поведение:

const asyncFunction = jest.fn(() => Promise.resolve('resolved value'));

test('tests async function', async () => {
  const result = await asyncFunction();
  expect(result).toBe('resolved value');
  expect(asyncFunction).toHaveBeenCalled();
});

В данном примере asyncFunction — это мок, который возвращает промис, и мы тестируем его поведение с использованием async/await.

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

Jest позволяет управлять состоянием моков через методы восстановления их исходного состояния или изменения поведения. Например, после использования jest.spyOn() для отслеживания методов можно восстановить исходное состояние метода:

const spy = jest.spyOn(myObject, 'doSomething');
spy.mockRestore();  // Восстановление оригинального метода

Так же можно очистить состояние всех моков после каждого теста с помощью jest.clearAllMocks():

afterEach(() => {
  jest.clearAllMocks();
});

Это необходимо для того, чтобы тесты не имели побочных эффектов между собой.

Мокирование зависимостей в классовых компонентах

В классовых компонентах часто встречаются методы, которые зависят от внешних сервисов или API. Мокирование таких зависимостей помогает тестировать бизнес-логику компонента без необходимости реальных запросов.

class MyComponent extends React.Component {
  componentDidMount() {
    this.props.fetchData().then(data => this.setState({ data }));
  }

  render() {
    return <div>{this.state.data}</div>;
  }
}

test('fetches data on mount', async () => {
  const fetchDataMock = jest.fn(() => Promise.resolve('mocked data'));
  const wrapper = shallow(<MyComponent fetchData={fetchDataMock} />);
  
  await wrapper.instance().componentDidMount();
  
  expect(fetchDataMock).toHaveBeenCalled();
  expect(wrapper.state().data).toBe('mocked data');
});

В этом примере мы замокировали проп fetchData, который использует компонент для получения данных, и проверили, что данные были правильно установлены в состояние компонента.

Заключение

Мокирование в Jest — мощный инструмент для тестирования компонентов и функций в изоляции. Используя методы jest.fn(), jest.mock() и jest.spyOn(), можно эффективно заменять зависимости и проверять поведение тестируемого кода. Это позволяет создавать независимые, быстрые и стабильные тесты, которые не зависят от внешних сервисов или сложных взаимодействий с другими частями системы.