Замена реализации через and.callFake

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

Создание шпиона и подмена функции

Шпион создаётся с помощью функции spyOn, которая принимает объект и имя метода:

let calculator = {
    add: function(a, b) {
        return a + b;
    }
};

spyOn(calculator, 'add');

После такой подстановки метод calculator.add перестает выполнять оригинальную логику, если явно не указано иное. Чтобы заменить его поведение на кастомное, используется and.callFake.

calculator.add.and.callFake(function(a, b) {
    return a * b; // новая логика: умножение вместо сложения
});

console.log(calculator.add(2, 3)); // 6

Ключевой момент: and.callFake полностью переопределяет оригинальную функцию на время теста.

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

Подмена через and.callFake позволяет создавать динамическое поведение, зависящее от аргументов:

let api = {
    fetchData: function(id) {
        return { id: id, value: 'original' };
    }
};

spyOn(api, 'fetchData').and.callFake(function(id) {
    if (id === 1) {
        return { id: id, value: 'mocked for 1' };
    }
    return { id: id, value: 'default mock' };
});

console.log(api.fetchData(1)); // { id: 1, value: 'mocked for 1' }
console.log(api.fetchData(2)); // { id: 2, value: 'default mock' }

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

Сочетание с expect и проверкой вызовов

Даже при замене функции на кастомную, шпион продолжает сохранять информацию о вызовах:

spyOn(calculator, 'add').and.callFake((a, b) => a - b);

calculator.add(10, 3);

expect(calculator.add).toHaveBeenCalled();
expect(calculator.add).toHaveBeenCalledWith(10, 3);
expect(calculator.add(5, 2)).toBe(3);

Таким образом, and.callFake сочетает две возможности: управляемое поведение функции и сохранение статистики вызовов для ассертов.

Применение в асинхронных функциях

Для функций, возвращающих промисы, and.callFake позволяет эмулировать асинхронное поведение без реальных сетевых запросов:

let api = {
    fetchUser: function(id) {
        return fetch(`/users/${id}`).then(res => res.json());
    }
};

spyOn(api, 'fetchUser').and.callFake(function(id) {
    return Promise.resolve({ id: id, name: 'Test User' });
});

api.fetchUser(5).then(user => {
    console.log(user.name); // Test User
});

Такой подход значительно ускоряет тестирование и исключает зависимость от внешних сервисов.

Ограничения и особенности

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

Пример сложной подмены с условием и сохранением контекста

let userService = {
    currentUser: { id: 1, name: 'Alice' },
    getUserName: function(prefix) {
        return `${prefix}: ${this.currentUser.name}`;
    }
};

spyOn(userService, 'getUserName').and.callFake(function(prefix) {
    if (this.currentUser.id === 1) {
        return `${prefix}: mocked ${this.currentUser.name}`;
    }
    return 'unknown';
});

console.log(userService.getUserName('Hello')); // Hello: mocked Alice

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

Практическое применение

and.callFake часто используется для:

  • Мокирования сетевых запросов и асинхронных операций.
  • Тестирования граничных условий и ошибок без изменения основного кода.
  • Эмуляции сложной логики, зависящей от входных данных.
  • Повышения скорости тестирования за счет исключения тяжёлых операций (например, взаимодействие с базой данных или API).

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