Mock Functions
Мокинг упрощает тестирование соединений в коде за счет игнорирования деталей реализации функций, захвата вызовов функций (и параметров переданных при вызове), захвата экземпляров конструкторов при создании за счет вызова new
, а также позволяя конфигурировать возвращаемые значения во время тестирования.
There are two ways to mock functions: Either by creating a mock function to use in test code, or writing a manual mock
to override a module dependency.
Using a mock function #
Let's imagine we're testing an implementation of a function forEach
, which invokes a callback for each item in a supplied array.
function forEach(items, callback) { for (let index = 0; index < items.length; index++) { callback(items[index]); } }
To test this function, we can use a mock function, and inspect the mock's state to ensure the callback is invoked as expected.
const mockCallback = jest.fn(); forEach([0, 1], mockCallback); // The mock function is called twice expect(mockCallback.mock.calls.length).toBe(2); // The first argument of the first call to the function was 0 expect(mockCallback.mock.calls[0][0]).toBe(0); // The first argument of the second call to the function was 1 expect(mockCallback.mock.calls[1][0]).toBe(1);
.mock
свойство #
Все мокинг функции имеют специальное .mock
свойство, в котором хранятся данные о том, как функция вызывалась. .mock
свойство также следит за значением this
при каждом вызове для того, чтобы его было возможно изучить в дальнейшем:
const myMock = jest.fn(); const a = new myMock(); const b = {}; const bound = myMock.bind(b); bound(); console.log(myMock.mock.instances); // > [ <a>, <b> ]
Все эти мокинг члены экземпляра очень полезны в тестах при проверке того, как функции вызываются или экземпляризуются:
// Функция была вызвана ровно один раз expect(someMockFunction.mock.calls.length).toBe(1); // Первый аргумент при первом вызове функции равен 'первый аргумент' expect(someMockFunction.mock.calls[0][0]).toBe('первый аргумент'); // Второй аргумент при первом вызове функции равен 'второй аргумент' expect(someMockFunction.mock.calls[0][1]).toBe('второй аргумент'); // Функция использовалась для создания экземпляров ровно два раза expect(someMockFunction.mock.instances.length).toBe(2); // Объект возвращенный при первом вызове функции для создания экземпляра // имеет свойство `name`, которое содержит значение 'тест' expect(someMockFunction.mock.instances[0].name).toEqual('тест');
Значения возвращаемые имитаторами #
Имитаторы могут быть использованы для инъекции значений в ваш код во время тестирования:
const myMock = jest.fn(); console.log(myMock()); // > undefined myMock.mockReturnValueOnce(10) .mockReturnValueOnce('значение') .mockReturnValue(true); console.log(myMock(), myMock(), myMock(), myMock()); // > 10, 'значение', true, true
Имитаторы также очень эффективны в коде, который использует функциональный стиль передачи континуации. Код, написанный в данном стиле помогает избежать нужды в усложненных заглушках, которые воссоздают поведение замененного ими реального компонента, и предпочесть введение данных напрямую в тест перед тем, как они используются.
const filterTestFn = jest.fn(); // Мок возвращает `true` при первом вызове // и `false` при втором вызове filterTestFn .mockReturnValueOnce(true) .mockReturnValueOnce(false); const result = [11, 12].filter(filterTestFn); console.log(result); // > [11] console.log(filterTestFn.mock.calls); // > [ [11], [12] ]
На самом деле, большинство реальных примеров включают получение доступа к имитатору зависимого компонента и его конфигурации, но при этом техника идентична. В таких случаях, попытайтесь избежать искушения реализовать логику внутри функций, которые не тестируется напрямую.
Реализации имитаторов #
Тем не менее есть случаи, когда полезно выйти за рамки возможности указывать возвращаемые значения и полностью заменить реализацию имитатора. Это может быть осуществлено при помощи функции jest.fn
или метода mockImplementationOnce
самого имитатора.
const myMockFn = jest.fn(cb => cb(null, true)); myMockFn((err, val) => console.log(val)); // > true myMockFn((err, val) => console.log(val)); // > true
Метод mockImplementation
полезен, когда необходимо указать реализацию по-умолчанию для имитатора созданного в другом модуле:
// foo.js module.exports = function() { // реализация; }; // test.js jest.mock('../foo'); // происходит автоматически при авто-мокинге const foo = require('../foo'); // foo это мок функция foo.mockImplementation(() => 42); foo(); // > 42
В случаях когда требуется воссоздать сложное поведение имитатора, к примеру множественные вызовы возвращающие разные результаты, используйте метод mockImplementationOnce
:
const myMockFn = jest.fn() .mockImplementationOnce(cb => cb(null, true)) .mockImplementationOnce(cb => cb(null, false)); myMockFn((err, val) => console.log(val)); // > true myMockFn((err, val) => console.log(val)); // > false
When the mocked function runs out of implementations defined with mockImplementationOnce
, it will execute the default implementation set with jest.fn
(if it is defined):
const myMockFn = jest.fn(() => 'по умолчанию') .mockImplementationOnce(() => 'первый вызов') .mockImplementationOnce(() => 'второй вызов'); console.log(myMockFn(), myMockFn(), myMockFn(), myMockFn()); // > 'первый вызов', 'второй вызов', 'по умолчанию', 'по умолчанию'
В случаях, когда используются методы, соединенные в цепь вызовов (и следовательно они всегда должны возвращать this
), Jest предоставляет декоративный API в форме функции .mockReturnThis()
, которая доступна для всех имитаторов:
const myObj = { myMethod: jest.fn().mockReturnThis(), }; // тоже самое что const otherObj = { myMethod: jest.fn(function() { return this; }), };
Пользовательские матчеры #
Наконец, для того, чтобы упростить утверждения о вызовах имитаторов, мы добавили некоторые индивидуальные вычислители:
// Мок функция вызывалась по крайней мере один раз expect(mockFunc).toBeCalled(); // Мок функция вызывалась по крайней мере один раз с указанными аргументами expect(mockFunc).toBeCalledWith(arg1, arg2); // Последний вызов мок функции осуществлялся с указанными аргументами expect(mockFunc).lastCalledWith(arg1, arg2);
Данные матчеры по сути лишь обертка для распространенных способов проверки .mock
свойства. Вы всегда можете достичь того же результата в ручную если это вам больше по вкусу или если вы нуждаетесь в чем-то более конкретном:
// Мок функция вызывалась по крайней мере один раз expect(mockFunc.mock.calls.length).toBeGreaterThan(0); // Мок функция вызывалась по крайней мере один раз с указанными аргументами expect(mockFunc.mock.calls).toContain([arg1, arg2]); // Последний вызов мок функции осуществлялся с указанными аргументами expect(mockFunc.mock.calls[mockFunc.mock.calls.length - 1]).toEqual( [arg1, arg2] ); // Первый аргумент при последнем вызове мок функции равен `42` // (обратите внимание на то, что для данного утверждения нет вспомогательной обертки) expect(mockFunc.mock.calls[mockFunc.mock.calls.length - 1][0]).toBe(42);
Для полного списка матчеров, обратите внимание на справочную документацию.