Fetch API mocking

Тестирование в JavaScript невозможно без проверки асинхронных операций, таких как HTTP-запросы. В реальных приложениях часто используется API для обмена данными между клиентом и сервером, и такие запросы должны быть проверены. В Jasmine для этих целей можно использовать технику mocking (мока). Это позволяет эмулировать поведение сетевых запросов без необходимости реально обращаться к серверу.

Зачем использовать mock для Fetch API?

Использование реальных сетевых запросов в тестах может замедлить выполнение тестов и зависеть от внешних факторов, таких как доступность сервера и стабильность интернет-соединения. Mocking позволяет контролировать поведение запросов, не зависеть от внешней среды и тестировать только логику работы приложения.

Основы Fetch API

Метод fetch является встроенной функцией для выполнения асинхронных HTTP-запросов. Он возвращает Promise, который резолвится в объект Response. Ответ можно обрабатывать с помощью методов json(), text(), blob() и других.

Пример использования fetch:

fetch('https://api.example.com/data')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error(error));

Этот код отправляет GET-запрос на указанный URL и выводит результат в консоль. Однако, в тестах нам нужно будет эмулировать этот процесс, не делая реальных HTTP-запросов.

Mocking Fetch API в Jasmine

В Jasmine для мока сетевых запросов можно использовать глобальный объект spyOn. С помощью этой функции можно заменить реальную реализацию метода fetch на поддельную, которая будет возвращать заранее подготовленные ответы.

Шаг 1: Подготовка моков

Для начала нужно создать фиктивную реализацию функции fetch. Пример:

beforeEach(() => {
  spyOn(window, 'fetch').and.returnValue(Promise.resolve({
    json: () => Promise.resolve({ data: 'test data' })
  }));
});

В этом примере мы заменяем fetch на фейковую версию, которая всегда возвращает объект Response с методом json, который резолвится в объект с тестовыми данными.

Шаг 2: Написание теста с мокированным fetch

Теперь, когда мы замокали fetch, можно писать тесты. Рассмотрим пример:

it('должен сделать запрос и вернуть данные', async () => {
  const data = await fetchDataFromApi(); // наша функция, использующая fetch

  expect(data).toEqual({ data: 'test data' });
  expect(fetch).toHaveBeenCalledWith('https://api.example.com/data');
});

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

Шаг 3: Проверка ошибок

Важно также тестировать, как ваше приложение будет вести себя при ошибках. Для этого можно мокировать ошибку в fetch:

beforeEach(() => {
  spyOn(window, 'fetch').and.returnValue(Promise.reject('Ошибка'));
});

it('должен обработать ошибку при запросе', async () => {
  try {
    await fetchDataFromApi();
  } catch (error) {
    expect(error).toBe('Ошибка');
  }
});

Здесь мы мокируем fetch, чтобы он всегда резолвился с ошибкой. В тесте проверяется, что ошибка правильно обрабатывается и передается в catch блок.

Использование других фреймворков для мока

В Jasmine можно использовать другие библиотеки и инструменты для более сложного мока. Одним из таких инструментов является Sinon.js, который предоставляет дополнительные возможности для создания моков, спаев и фейков.

Пример использования Sinon для мока fetch:

import sinon from 'sinon';

let fetchStub;

beforeEach(() => {
  fetchStub = sinon.stub(window, 'fetch').resolves({
    json: () => Promise.resolve({ data: 'test data' })
  });
});

afterEach(() => {
  fetchStub.restore();
});

it('должен вернуть данные через мокированное API', async () => {
  const data = await fetchDataFromApi();
  expect(data).toEqual({ data: 'test data' });
  expect(fetchStub.calledOnce).toBe(true);
});

Особенности работы с ответами

При мокировании fetch можно возвращать различные типы ответов. В примерах выше использовался метод json(), но можно мокировать другие методы, такие как:

  • text(): для получения текстовых данных.
  • blob(): для работы с бинарными данными.
  • ok: чтобы контролировать успешность ответа.

Пример мока с проверкой свойства ok:

beforeEach(() => {
  spyOn(window, 'fetch').and.returnValue(Promise.resolve({
    ok: true,
    json: () => Promise.resolve({ data: 'test data' })
  }));
});

it('должен проверять успешность ответа', async () => {
  const response = await fetch('https://api.example.com/data');
  expect(response.ok).toBe(true);
  const data = await response.json();
  expect(data).toEqual({ data: 'test data' });
});

Динамическое изменение моков

В Jasmine можно менять поведение мока в зависимости от конкретных тестов. Например, для одного теста можно мокировать успешный ответ, а для другого — ошибку:

it('должен обработать успешный ответ', async () => {
  spyOn(window, 'fetch').and.returnValue(Promise.resolve({
    json: () => Promise.resolve({ data: 'test data' })
  }));

  const data = await fetchDataFromApi();
  expect(data).toEqual({ data: 'test data' });
});

it('должен обработать ошибку при запросе', async () => {
  spyOn(window, 'fetch').and.returnValue(Promise.reject('Ошибка'));

  try {
    await fetchDataFromApi();
  } catch (error) {
    expect(error).toBe('Ошибка');
  }
});

Mocking fetch с параметрами

Для более сложных тестов важно учитывать параметры запроса. Например, можно проверять, был ли запрос отправлен с нужными заголовками или методами:

beforeEach(() => {
  spyOn(window, 'fetch').and.callFake((url, options) => {
    expect(url).toBe('https://api.example.com/data');
    expect(options.method).toBe('POST');
    expect(options.headers['Content-Type']).toBe('application/json');
    return Promise.resolve({
      json: () => Promise.resolve({ data: 'test data' })
    });
  });
});

it('должен отправить POST-запрос с правильными заголовками', async () => {
  const data = await fetchDataFromApi();
  expect(data).toEqual({ data: 'test data' });
});

В этом примере мы проверяем, что запрос был отправлен методом POST, с нужным URL и заголовками.

Заключение

Mocking fetch в Jasmine — это мощный инструмент для тестирования асинхронных операций без реальных сетевых запросов. Он позволяет контролировать ответы, исключать зависимость от внешних факторов и ускорять выполнение тестов. Важно помнить о правильной настройке моков и их проверке, чтобы тесты были точными и воспроизводимыми.