Mocking для скорости

Mocking (или создание макетов) — это процесс замены реальных зависимостей или объектов в тестах на фиктивные версии, которые имитируют их поведение. Это важный аспект тестирования, так как позволяет быстрее и эффективнее проводить юнит-тесты, изолируя тестируемый код от внешних факторов, таких как базы данных, API или сторонние сервисы.

В Jasmine mocking используется для создания поддельных версий функций, объектов или методов, которые контролируют взаимодействие с внешними системами. Такой подход повышает скорость тестирования и дает возможность точнее изолировать тестируемый функционал.

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

  1. Ускорение тестов: Использование реальных зависимостей может замедлить выполнение тестов. Например, обращение к базе данных или API требует времени на сетевые запросы и обработки, что значительно увеличивает время тестирования.
  2. Изоляция от внешних факторов: Внешние зависимости могут изменяться, что может повлиять на стабильность тестов. Mocking позволяет гарантировать, что тесты всегда выполняются в одинаковых условиях.
  3. Упрощение тестирования сложных сценариев: Некоторые внешние сервисы или базы данных могут быть трудными для эмуляции, или их состояние может зависеть от множества факторов. Mocking позволяет имитировать поведение таких сервисов в заранее заданных сценариях.

Основы использования Mocking в Jasmine

Jasmine предоставляет несколько инструментов для работы с mock-объектами, основными из которых являются функции spyOn, mock и stub.

spyOn

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

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

describe('Тестирование функции с использованием spyOn', function() {
  it('должен вызывать метод sendData', function() {
    var service = {
      sendData: function(data) {
        return 'data sent';
      }
    };
    
    // Создаём шпион на методе sendData
    spyOn(service, 'sendData').and.callThrough();
    
    service.sendData('test');
    
    // Проверка, что метод был вызван
    expect(service.sendData).toHaveBeenCalled();
  });
});

В этом примере мы подменили метод sendData в объекте service с помощью spyOn. Метод callThrough() позволяет вызвать оригинальную реализацию метода после перехвата. Также можно использовать and.returnValue(value), чтобы задать конкретный результат для метода.

Статическая подстановка значения

Если нужно заменить возвращаемое значение без выполнения оригинальной логики, можно использовать and.returnValue:

describe('Тестирование с возвращаемым значением', function() {
  it('должен вернуть заглушенное значение', function() {
    var service = {
      getData: function() {
        return 'real data';
      }
    };
    
    spyOn(service, 'getData').and.returnValue('mock data');
    
    expect(service.getData()).toBe('mock data');
  });
});

Здесь метод getData всегда будет возвращать 'mock data', независимо от того, что происходит в оригинальной реализации.

Заглушки (Stubs)

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

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

describe('Заглушка для асинхронного вызова', function() {
  it('должен использовать mock для асинхронного метода', function(done) {
    var service = {
      fetchData: function(callback) {
        setTimeout(function() {
          callback('real data');
        }, 1000);
      }
    };

    spyOn(service, 'fetchData').and.callFake(function(callback) {
      callback('mock data');
      done();
    });
    
    service.fetchData(function(result) {
      expect(result).toBe('mock data');
    });
  });
});

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

Пример работы с классами

Mocking также применим при тестировании классов и их методов. Рассмотрим пример, где класс взаимодействует с внешним API:

class ApiService {
  fetchData() {
    return fetch('https://api.example.com/data').then(response => response.json());
  }
}

describe('Тестирование ApiService с использованием mock', function() {
  it('должен использовать мок для fetch', function(done) {
    var apiService = new ApiService();
    
    spyOn(window, 'fetch').and.returnValue(Promise.resolve({
      json: function() {
        return Promise.resolve({ message: 'mock data' });
      }
    }));

    apiService.fetchData().then(function(data) {
      expect(data.message).toBe('mock data');
      done();
    });
  });
});

Здесь метод fetch глобального объекта window заменён на поддельную версию, которая возвращает заранее заданные данные. Это позволяет протестировать работу метода fetchData без реального выполнения HTTP-запроса.

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

  • Восстановление оригинальных функций: После использования spyOn, оригинальные функции можно восстановить с помощью метода and.callThrough(), который восстанавливает исходное поведение. Также можно использовать mockRestore для восстановления состояния в более сложных случаях.

  • Асинхронность: При тестировании асинхронных функций важно корректно работать с done() для уведомления Jasmine о завершении теста. Для промисов можно использовать .then() для обработки результата.

  • Сложные зависимости: Если тестируемая функция зависит от нескольких сложных объектов, можно использовать несколько spyOn и заглушек для всех этих зависимостей. Однако следует избегать слишком сложных mock-объектов, так как это может усложнить поддержку тестов.

  • Гибкость: Jasmine предоставляет гибкие инструменты для имитации поведения объектов и функций. Вместо того чтобы создавать полноценные фиктивные объекты, можно использовать and.callFake, чтобы точно контролировать поведение во время тестов.

Заключение

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