Mock Functions
Функції-імітації значно спрощують тестування пов’язаного коду, надаючи можливість стирати справжню імплементацію функцї, записувати виклики функції (і параметри, які були їй передані), записувати екземпляри, які повертає функція-конструктор, викликана з допомогою оператора new
і вказувати значення, які має повернути функція під час тестування.
Існує два способи створення імітацій функцій: створення функцій-імітацій в коді тестів або написання ручної імітації
для перевизначення залежності модуля.
Використвання імітацій функцій #
Давайте уявимо, що ми тестуємо реалізацію функції forEach
, яка викликає коллбек для кожного елементу в наданому масиві.
function forEach(items, callback) { for (let index = 0; index < items.length; index++) { callback(items[index]); } }
Щоб протестувати цю функцію, ми можемо використати функцію-імітацію і перевірити стан імітації, щоб переконатися, що зворотній виклик було викликано правильно.
const mockCallback = jest.fn(); forEach([0, 1], mockCallback); // Функція-імітація викликана двічі expect(mockCallback.mock.calls.length).toBe(2); // Перший аргумент першого виклику функції був 0 expect(mockCallback.mock.calls[0][0]).toBe(0); // Перший аргумент другого виклику функції був 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> ]
Наступні властивості імітацій дуже корисні в тестах для перевірки того, які функції були викликані або які екземпляри були створені:
// 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'); // 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');
Імітація повернених значень #
Імітації функцій також можуть бути використані щоб передавати тестові значення у ваш код під час тесту:
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(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() { // some implementation; }; // test.js 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(), }; // це те саме що const otherObj = { myMethod: jest.fn(function() { return this; }), };
Користувацькі матчери #
Нарешті, для того, щоб спростити перевірку як імітації функцій були викликані, ми додали наступні матчери:
// The mock function was called at least once expect(mockFunc).toBeCalled(); // The mock function was called at least once with the specified args expect(mockFunc).toBeCalledWith(arg1, arg2); // The last call to the mock function was called with the specified args expect(mockFunc).lastCalledWith(arg1, arg2);
Ці матчери насправді всього лише синтаксичний цукор для найпоширеніших форм перевірки властивості .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).toContain([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);
Для повного списку матчерів зверніться до довідкової документації.