Моковые функции
Моковые функции позволяют тестировать связи между кодом, заменяя фактическое выполнение функции, сохраняя вызовы функции (и параметры, переданные в этих вызовах), сохраняя экземпляры конструкторских функций при создании с new, и позволяя настраивать значения возвращаемых результатов во время тестирования.
Существует два способа смоделировать функции: либо создать моковую функцию для использования в тестовом коде, либо написать manual mock для переопределения зависимости модуля.
Использование моковой функции
Предположим, мы тестируем реализацию функции forEach, которая вызывает обратный вызов для каждого элемента в предоставленном массиве.
function forEach(items, callback) {
for (let index = 0; index < items.length; index++) {
callback(items[index]);
}
}
Для тестирования этой функции мы можем использовать моковую функцию и проверить состояние мока, чтобы убедиться, что обратный вызов вызывается как ожидается.
const mockCallback = jest.fn(x => 42 + x); 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); // The return value of the first call to the function was 42 expect(mockCallback.mock.results[0].value).toBe(42);
.mock свойство
Все моковые функции имеют это специальное .mock свойство, в котором хранятся данные о том, как функция была вызвана и что функция вернула. .mock свойство также отслеживает значение this для каждого вызова, поэтому его также можно проверить:
const myMock1 = jest.fn();
const a = new myMock1();
console.log(myMock1.mock.instances);
// > [ <a> ]
const myMock2 = jest.fn();
const b = {};
const bound = myMock2.bind(b);
bound();
console.log(myMock2.mock.contexts);
// > [ <b> ]
Эти члены мока очень полезны в тестах для проверки того, как эти функции вызываются, создаются или что они возвращают:
// The function was called exactly once
expect(someMockFunction.mock.calls.length).toBe(1);
// The first arg of the first call to the function was 'first arg'
expect(someMockFunction.mock.calls[0][0]).toBe('first arg');
// The second arg of the first call to the function was 'second arg'
expect(someMockFunction.mock.calls[0][1]).toBe('second arg');
// The return value of the first call to the function was 'return value'
expect(someMockFunction.mock.results[0].value).toBe('return value');
// The function was called with a certain `this` context: the `element` object.
expect(someMockFunction.mock.contexts[0]).toBe(element);
// This function was instantiated exactly twice
expect(someMockFunction.mock.instances.length).toBe(2);
// The object returned by the first instantiation of this function
// had a `name` property whose value was set to 'test'
expect(someMockFunction.mock.instances[0].name).toEqual('test');
// The first argument of the last call to the function was 'test'
expect(someMockFunction.mock.lastCall[0]).toBe('test');
Моковые значения возврата
Моковые функции также могут использоваться для вставки тестовых значений в ваш код во время тестирования:
const myMock = jest.fn();
console.log(myMock());
// > undefined
myMock.mockReturnValueOnce(10).mockReturnValueOnce('x').mockReturnValue(true);
console.log(myMock(), myMock(), myMock(), myMock());
// > 10, 'x', true, true
Моковые функции также очень эффективны в коде, использующем функциональный стиль передачи продолжения. Код, написанный в этом стиле, помогает избежать необходимости в сложных заглушках, которые воссоздают поведение реального компонента, вместо этого вводя значения непосредственно в тест перед использованием.
const filterTestFn = jest.fn(); // Make the mock return `true` for the first call, // and `false` for the second call filterTestFn.mockReturnValueOnce(true).mockReturnValueOnce(false); const result = [11, 12].filter(num => filterTestFn(num)); console.log(result); // > [11] console.log(filterTestFn.mock.calls[0][0]); // 11 console.log(filterTestFn.mock.calls[1][0]); // 12
Большинство реальных примеров фактически подразумевают получение моковой функции из зависимого компонента и настройку этой функции, но техника остаётся одинаковой. В этих случаях старайтесь избегать соблазна реализовывать логику внутри любой функции, которая не тестируется непосредственно.
Моделирование модулей
Предположим, у нас есть класс, который извлекает пользователей из нашего API. Класс использует axios для вызова API, а затем возвращает data атрибут, содержащий всех пользователей:
import axios from 'axios';
class Users {
static all() {
return axios.get('/users.json').then(resp => resp.data);
}
}
export default Users;
Теперь, чтобы протестировать этот метод без фактического обращения к API (что создаёт медленные и хрупкие тесты), мы можем использовать функцию jest.mock(...) для автоматического моделирования модуля axios.
После моделирования модуля мы можем предоставить mockResolvedValue для .get, который возвращает данные, с которыми наш тест должен сопоставить. По сути, мы говорим, что хотим, чтобы axios.get('/users.json') возвращал поддельный ответ.
import axios from 'axios';
import Users from './users';
jest.mock('axios');
test('should fetch users', () => {
const users = [{name: 'Bob'}];
const resp = {data: users};
axios.get.mockResolvedValue(resp);
// or you could use the following depending on your use case:
// axios.get.mockImplementation(() => Promise.resolve(resp))
return Users.all().then(data => expect(data).toEqual(users));
});
Моделирование частей
Можно смоделировать подмножества модуля, а остальная часть модуля может сохранять свою фактическую реализацию:
export const foo = 'foo'; export const bar = () => 'bar'; export default () => 'baz';
//test.js
import defaultExport, {bar, foo} from '../foo-bar-baz';
jest.mock('../foo-bar-baz', () => {
const originalModule = jest.requireActual('../foo-bar-baz');
//Mock the default export and named export 'foo'
return {
__esModule: true,
...originalModule,
default: jest.fn(() => 'mocked baz'),
foo: 'mocked foo',
};
});
test('should do a partial mock', () => {
const defaultExportResult = defaultExport();
expect(defaultExportResult).toBe('mocked baz');
expect(defaultExport).toHaveBeenCalled();
expect(foo).toBe('mocked foo');
expect(bar()).toBe('bar');
});
Реализации мока
Тем не менее, есть случаи, когда полезно выйти за рамки возможности указать значения возврата и полностью заменить реализацию моковой функции. Это можно сделать с помощью jest.fn или mockImplementationOnce метода моковых функций.
const myMockFn = jest.fn(cb => cb(null, true)); myMockFn((err, val) => console.log(val)); // > true
Метод mockImplementation полезен, когда вам нужно определить стандартную реализацию моковой функции, созданной из другого модуля:
module.exports = function () {
// some implementation;
};
jest.mock('../foo'); // this happens automatically with automocking
const foo = require('../foo');
// foo is a mock function
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
Когда смоделированная функция исчерпывает реализации, определённые с помощью mockImplementationOnce, она выполняет стандартную реализацию, заданную с помощью jest.fn (если она определена):
const myMockFn = jest .fn(() => 'default') .mockImplementationOnce(() => 'first call') .mockImplementationOnce(() => 'second call'); console.log(myMockFn(), myMockFn(), myMockFn(), myMockFn()); // > 'first call', 'second call', 'default', 'default'
В случаях, когда у нас есть методы, которые обычно объединяются (и, следовательно, всегда должны возвращать this), у нас есть удобный API для упрощения этого в виде функции .mockReturnThis(), которая также присутствует на всех моках:
const myObj = {
myMethod: jest.fn().mockReturnThis(),
};
// is the same as
const otherObj = {
myMethod: jest.fn(function () {
return this;
}),
};
Имена моков
Вы можете необязательно указать имя для своих моковых функций, которое будет отображаться вместо «jest.fn()» в выводе ошибки теста. Используйте это, если вы хотите быстро идентифицировать моковую функцию, сообщающую об ошибке в выводе вашего теста.
const myMockFn = jest
.fn()
.mockReturnValue('default')
.mockImplementation(scalar => 42 + scalar)
.mockName('add42');
Пользовательские соответствия
Наконец, чтобы уменьшить сложность проверки того, как вызывались моковые функции, мы добавили для вас несколько пользовательских функций соответствия:
// The mock function was called at least once expect(mockFunc).toHaveBeenCalled(); // The mock function was called at least once with the specified args expect(mockFunc).toHaveBeenCalledWith(arg1, arg2); // The last call to the mock function was called with the specified args expect(mockFunc).toHaveBeenLastCalledWith(arg1, arg2); // All calls and the name of the mock is written as a snapshot expect(mockFunc).toMatchSnapshot();
Эти соответствия являются удобным способом проверки свойства .mock. Вы всегда можете сделать это вручную, если это удобнее или если вам нужно сделать что-то более специфическое:
// The mock function was called at least once
expect(mockFunc.mock.calls.length).toBeGreaterThan(0);
// The mock function was called at least once with the specified args
expect(mockFunc.mock.calls).toContainEqual([arg1, arg2]);
// The last call to the mock function was called with the specified args
expect(mockFunc.mock.calls[mockFunc.mock.calls.length - 1]).toEqual([
arg1,
arg2,
]);
// The first arg of the last call to the mock function was `42`
// (note that there is no sugar helper for this specific of an assertion)
expect(mockFunc.mock.calls[mockFunc.mock.calls.length - 1][0]).toBe(42);
// A snapshot will check that a mock was invoked the same number of times,
// in the same order, with the same arguments. It will also assert on the name.
expect(mockFunc.mock.calls).toEqual([[arg1, arg2]]);
expect(mockFunc.getMockName()).toBe('a mock name');
Полный список соответствий можно найти в документации.
© 2022 Facebook, Inc.
Licensed under the MIT License.
https://jestjs.io/docs/mock-functions