Mocking внешних dependencies

В процессе разработки часто возникает необходимость в том, чтобы тесты не зависели от внешних сервисов, API или сложных внутренних компонентов. Для этого используется подход, известный как mocking (мокинг). Он позволяет создавать подделки (mock-объекты) для сложных зависимостей, чтобы тестировать лишь саму логику компонента, а не его взаимодействие с внешними сервисами или базой данных. В Jasmine mocking является важной частью создания изолированных и эффективных юнит-тестов.

Основные понятия мокинга

  • Mock (мок): Подделка объекта или функции, которая заменяет реальный компонент в тестах. Моки могут быть настроены так, чтобы возвращать определённые значения, имитировать вызовы и даже проверять, как были вызваны методы.
  • Stub (стаб): Подмокающая версия функции или метода, предназначенная для замены реальной реализации на время теста. Обычно стаб возвращает заранее определённые данные, но не выполняет реальную логику.
  • Spy (шпион): Это функция или объект, который отслеживает, были ли вызваны методы или функции, с какими аргументами они были вызваны, сколько раз они были вызваны и т. д. В Jasmine шпионы широко используются для проверки взаимодействий между компонентами.

Как работает мокинг в Jasmine

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

spyOn для мокинга функций

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

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

describe('Тестирование метода с мокингом', function() {
  it('должен вызывать метод calculate один раз', function() {
    const calculator = {
      calculate: function(a, b) { return a + b; }
    };

    spyOn(calculator, 'calculate');

    calculator.calculate(2, 3);

    expect(calculator.calculate).toHaveBeenCalled();
    expect(calculator.calculate).toHaveBeenCalledWith(2, 3);
  });
});

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

Настройка возвращаемых значений с and.returnValue

В случае, если необходимо, чтобы мок-функция возвращала определённое значение, можно использовать метод and.returnValue. Это позволяет задать поведение мокированной функции.

Пример:

describe('Тестирование с мокированием значения', function() {
  it('должен возвращать заданное значение', function() {
    const apiService = {
      fetchData: function() { return 'реальные данные'; }
    };

    spyOn(apiService, 'fetchData').and.returnValue('мокированные данные');

    const result = apiService.fetchData();

    expect(result).toBe('мокированные данные');
  });
});

Здесь мокируем метод fetchData и задаём ему возвращаемое значение — мокированные данные.

Использование and.callFake для создания кастомной логики

Если нужно, чтобы мок-функция выполняла кастомное поведение (например, обработку аргументов или возвращение значений в зависимости от условий), используется метод and.callFake.

Пример:

describe('Мокинг с кастомной логикой', function() {
  it('должен корректно обработать аргументы', function() {
    const apiService = {
      fetchData: function(id) { return 'данные для ' + id; }
    };

    spyOn(apiService, 'fetchData').and.callFake(function(id) {
      return 'мокированные данные для ' + id;
    });

    const result = apiService.fetchData(42);

    expect(result).toBe('мокированные данные для 42');
  });
});

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

Mocking внешних зависимостей с использованием фреймворков

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

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

describe('Мокинг с Sinon', function() {
  it('должен мокировать асинхронный запрос', function(done) {
    const request = sinon.stub().returns(Promise.resolve('мокированные данные'));

    request().then(function(result) {
      expect(result).toBe('мокированные данные');
      done();
    });
  });
});

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

Асинхронное тестирование требует особого подхода, так как часто нужно ожидать завершения операций, таких как запросы к серверу. В Jasmine можно комбинировать мокинг с асинхронными тестами, например, используя done() для отслеживания завершения операции.

Пример асинхронного теста с мокированием:

describe('Тестирование асинхронных операций', function() {
  it('должен вернуть мокированные данные после задержки', function(done) {
    const fetchDataAsync = function() {
      return new Promise((resolve) => {
        setTimeout(() => resolve('мокированные данные'), 100);
      });
    };

    spyOn(window, 'fetchDataAsync').and.callFake(fetchDataAsync);

    fetchDataAsync().then((data) => {
      expect(data).toBe('мокированные данные');
      done();
    });
  });
});

Здесь мы мокируем функцию fetchDataAsync, которая возвращает промис, и проверяем, что асинхронная операция завершится с нужным результатом.

Выводы и рекомендации

  • Изолированные тесты: Мокинг позволяет создать изолированные тесты, которые не зависят от внешних сервисов или базы данных.
  • Контроль поведения: С помощью моков и шпионов можно проверять вызовы методов и контролировать возвращаемые значения.
  • Асинхронные операции: Мокинг идеально подходит для тестирования асинхронных операций, таких как запросы к API или обработка данных.
  • Использование сторонних библиотек: Для более сложных случаев мокирования можно использовать такие библиотеки, как Sinon.js, которые расширяют возможности Jasmine.

Mocking является неотъемлемой частью юнит-тестирования, позволяя создавать предсказуемые и надежные тесты, которые не зависят от внешних компонентов.