Partial mocking объектов

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

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

Jasmine предоставляет два основных инструмента для работы с моками и шпионами:

  • Моки (Mocks) — объекты, которые позволяют замещать или фальсифицировать реализацию методов.
  • Шпионы (Spies) — функции, которые отслеживают вызовы методов, параметры и возвращаемые значения.

Для частичного мокирования используется именно шпион, так как он позволяет контролировать только определённые методы объекта, оставляя остальные неизменными.

Пример частичного мокирования

Предположим, есть объект userService, который имеет методы getUser(), saveUser() и deleteUser(). Для тестирования нужно замокать только метод getUser(), а остальные оставить без изменений. Пример кода:

describe('UserService', function() {
  var userService;

  beforeEach(function() {
    userService = {
      getUser: function(id) {
        return { id: id, name: 'John Doe' };
      },
      saveUser: function(user) {
        // сложная логика сохранения
      },
      deleteUser: function(id) {
        // сложная логика удаления
      }
    };
  });

  it('should mock only getUser method', function() {
    // Создаём шпион на методе getUser
    spyOn(userService, 'getUser').and.returnValue({ id: 1, name: 'Mocked User' });

    var user = userService.getUser(1);

    expect(user.name).toBe('Mocked User');
    expect(userService.getUser).toHaveBeenCalledWith(1);
  });
});

В этом примере создаётся объект userService с тремя методами. Мы замещаем только метод getUser с помощью spyOn, в то время как методы saveUser и deleteUser остаются в своей оригинальной реализации. Это позволяет проверить только изменённое поведение, не влияя на другие функции объекта.

Подробности использования spyOn для частичного мокирования

Метод spyOn в Jasmine предоставляет несколько важных функций для конфигурирования поведения замещаемого метода:

  • and.returnValue(value) — задаёт возвращаемое значение для замещаемого метода. Это полезно, когда нужно вернуть заранее определённый результат.
  • and.callFake(function) — позволяет задать свою функцию для замещаемого метода. Эта функция может содержать более сложную логику, чем просто возвращение значения.
  • and.callThrough() — позволяет выполнить оригинальный метод объекта, после чего можно анализировать его поведение.
  • and.callThrough() используется, когда необходимо проверить, что метод был вызван, но без его замены или изменения поведения.

Пример с использованием and.callFake:

describe('UserService', function() {
  var userService;

  beforeEach(function() {
    userService = {
      getUser: function(id) {
        return { id: id, name: 'John Doe' };
      },
      saveUser: function(user) {
        // сложная логика
      },
      deleteUser: function(id) {
        // сложная логика
      }
    };
  });

  it('should mock getUser with custom logic', function() {
    spyOn(userService, 'getUser').and.callFake(function(id) {
      return { id: id, name: 'Custom Mocked User' };
    });

    var user = userService.getUser(2);
    expect(user.name).toBe('Custom Mocked User');
  });
});

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

Частичное мокирование с сохранением оригинальной логики

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

Пример:

describe('UserService', function() {
  var userService;

  beforeEach(function() {
    userService = {
      getUser: function(id) {
        return { id: id, name: 'John Doe' };
      },
      saveUser: function(user) {
        // сложная логика
      },
      deleteUser: function(id) {
        // сложная логика
      }
    };
  });

  it('should mock getUser and call saveUser', function() {
    spyOn(userService, 'getUser').and.returnValue({ id: 1, name: 'Mocked User' });
    spyOn(userService, 'saveUser').and.callThrough();  // Оставляем оригинальную реализацию

    var user = userService.getUser(1);
    expect(user.name).toBe('Mocked User');
    
    userService.saveUser(user);
    expect(userService.saveUser).toHaveBeenCalled();
  });
});

В этом примере метод getUser замещается, а метод saveUser остаётся в своей оригинальной реализации. Таким образом, мы можем контролировать только необходимую часть поведения объекта, а остальную логику оставить без изменений.

Преимущества частичного мокирования

  • Контроль над конкретными методами: Частичное мокирование позволяет тестировать конкретные методы без необходимости мокировать все методы объекта.
  • Производительность: Так как замещаются только отдельные методы, это позволяет избежать излишней нагрузки на тестируемую систему и ускорить выполнение тестов.
  • Гибкость: Позволяет комбинировать замещение некоторых методов с сохранением оригинальных для других, что даёт возможность более точно настроить поведение объекта в тестах.

Заключение

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