Stubbing запросов

Stubbing является важной техникой в тестировании, позволяющей имитировать поведение зависимостей в тестах. Это особенно полезно, когда необходимо протестировать компонент или функцию, которые делают запросы к внешним ресурсам, таким как API или базы данных. Применение stubbing позволяет избежать реальных запросов во время тестирования, повысив скорость выполнения тестов и минимизируя внешние зависимости.

Зачем нужен stubbing?

Запросы к внешним ресурсам могут быть медленными и ненадежными, что делает тестирование таких компонентов сложным. В процессе работы могут возникнуть проблемы с сетью, изменениями в API или даже ограничениями на количество запросов. Чтобы изолировать тесты от этих факторов, stubbing позволяет заменить реальные вызовы на фиктивные. Это позволяет:

  • Снизить время выполнения тестов.
  • Убедиться в правильности работы кода без зависимости от внешних сервисов.
  • Проверить обработку ошибок, даже если внешний сервис недоступен.

Как работает stubbing в Jasmine?

Jasmine предоставляет мощные инструменты для замены реальных функций или объектов на заглушки (stubs). Основная цель стуба — это подменить настоящую реализацию функции или метода, чтобы она не выполняла реальные действия, а возвращала заранее подготовленный результат. Для этого в Jasmine используются такие методы, как spyOn и and.callFake.

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

Предположим, что необходимо протестировать функцию, которая делает HTTP-запрос через библиотеку fetch. Вместо того чтобы выполнять реальный запрос, мы можем использовать stubbing, чтобы вернуть заранее подготовленный ответ.

describe('Тестирование функции getUser', () => {
  it('должен вернуть данные пользователя', () => {
    // Создание подмены для метода fetch
    spyOn(window, 'fetch').and.callFake(() => {
      return Promise.resolve({
        json: () => Promise.resolve({ name: 'Иван', age: 30 })
      });
    });

    // Тестируемая функция
    function getUser() {
      return fetch('https://api.example.com/user')
        .then(response => response.json());
    }

    getUser().then(data => {
      expect(data.name).toBe('Иван');
      expect(data.age).toBe(30);
    });
  });
});

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

spyOn и его использование

Метод spyOn в Jasmine позволяет не только подменять методы объектов, но и отслеживать их вызовы. Это полезно для проверки того, как часто и с какими параметрами были вызваны функции.

Пример:

describe('Проверка вызова fetch', () => {
  it('должен вызвать fetch один раз', () => {
    spyOn(window, 'fetch').and.callFake(() => Promise.resolve({ json: () => Promise.resolve({}) }));

    fetch('https://api.example.com/data');

    expect(fetch).toHaveBeenCalledTimes(1);
  });
});

Здесь мы проверяем, что метод fetch был вызван ровно один раз. Для этого мы используем toHaveBeenCalledTimes(1) — матчере, который проверяет количество вызовов функции.

Подмена данных с помощью callFake и callThrough

Метод callFake позволяет задать свою имплементацию для подменяемой функции. Это дает возможность не только заменить функцию, но и контролировать её поведение.

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

describe('Пример с callThrough', () => {
  it('должен вызвать оригинальную функцию', () => {
    const originalFunction = jasmine.createSpy('original').and.callFake(() => 'fake result');
    
    const result = originalFunction();
    
    expect(originalFunction).toHaveBeenCalled();
    expect(result).toBe('fake result');
  });
});

Метод and.callThrough позволяет сохранить оригинальное поведение функции, и при этом можно дополнительно подменить или изменить ее результаты.

Тестирование с асинхронными запросами

Когда тестируемая функция использует асинхронные операции, такие как сетевые запросы, важно правильно работать с промисами или асинхронными функциями. Для этого в Jasmine можно использовать такие методы, как done, async/await или возвращение промиса.

Пример:

describe('Тестирование асинхронного запроса', () => {
  it('должен правильно обрабатывать ответ от API', (done) => {
    spyOn(window, 'fetch').and.callFake(() => {
      return Promise.resolve({
        json: () => Promise.resolve({ message: 'Success' })
      });
    });

    fetch('https://api.example.com/status')
      .then(response => response.json())
      .then(data => {
        expect(data.message).toBe('Success');
        done(); // Завершаем тест
      });
  });
});

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

Моки и стабы в реальных приложениях

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

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

describe('Тестирование работы с API', () => {
  it('должен обработать успешный ответ от API', () => {
    const apiServiceMock = {
      fetchData: jasmine.createSpy('fetchData').and.returnValue(Promise.resolve({ success: true }))
    };

    apiServiceMock.fetchData().then(data => {
      expect(data.success).toBe(true);
    });
  });
});

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

Подводные камни и ограничения

Stubbing может быть полезным, но важно понимать его ограничения. Например, чрезмерное использование stubbing может привести к тому, что тесты станут слишком изолированными от реального мира. Также стоит помнить, что stubbing должен использоваться в контексте изоляции компонентов и не должен заменять тестирование интеграций между системами.

Подмена внешних зависимостей может маскировать проблемы с интеграцией, которые возникают, когда компоненты взаимодействуют друг с другом в реальных условиях.