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);
Для повного списку матчерів зверніться до довідкової документації.