Модульное тестирование (Unit testing)
Что такое модульное тестирование?
Модульное тестирование - метод тестирования, с помощью которого тестируются отдельные модули, чтобы определить, есть ли какие-либо проблемы самим разработчиком. Речь идет о корректности работы автономных модулей.
Основная цель - изолировать каждую единицу системы для выявления, анализа и исправления дефектов.
Преимущества Модульное тестирования:
- Уменьшает количество дефектов в недавно разработанных функциях или уменьшает количество ошибок при изменении существующей функциональности.
- Снижает стоимость разработки, поскольку дефекты обнаруживаются на очень ранней стадии.
- Улучшает дизайн и позволяет лучше рефакторинг кода.
Недостатки модульного тестирования:
- Нельзя ожидать, что модульное тестирование выявит все ошибки в программе.
- Невозможно оценить все пути выполнения даже в самых тривиальных программах.
- Модульное тестирование по своей природе сосредоточено на единице кода. Следовательно, он не может обнаруживать ошибки интеграции или общие ошибки системного уровня.
Лучшие практики модульного тестирования
- Модульный тест должен быть независимыми. В случае каких-либо улучшений или изменений требований, это не должно повлиять на модульные тесты.
- Соблюдайте четкие и последовательные соглашения об именах для ваших модульных тестов.
- В случае изменения кода в каком-либо модуле убедитесь, что для модуля существует соответствующий test case, и модуль проходит тесты перед изменением реализации.
1function add(a,b){2 return a+b3}45test('Функция add должна возвращать 3',()=>{6 expect(sum(1, 2)).toBe(3);7})
Что такое модульный тест ?
Модульный тест - это автоматический тест, который:
- Проверяет небольшой фрагмент кода (также известный как модуль);
- Выполняет это быстро;
- Делает это изолированно;
Первые два атрибута здесь довольно однозначны. Могут возникнуть споры о том, что именно представляет собой быстрый модульный тест, потому что это в высшей степени субъективная мера. Но в целом это не так важно.
Test-Driven Development (TDD)
В конце 1990-х годов Кент Бек разработал технику «разработка через тестирование» (Test-Driven Development, TDD), как часть экстремального программирования. Эта техника для построения ПО, которая управляет процессом разработки через написание тестов. В сущности, повторяет три простых правила:
- Сначала пишется тест
- Затем пишется код под этот тест
- Рефакторинг нового и старого кода, чтобы улучшить качество кода
Процесс начинается заново пока не получится желаемый результат.
Написание теста первым дает два преимущества:
- Это способ получить само-тестируемый код
- Думая сначала о тесте вы заставляете себя думать об интерфейсе самого кода. Эта фокусировка на интерфейсе и на том как вы используете класс помогает вам разделить интерфейс от реализации.
Самая большая ошибка при использовании данной методологии — это пренебрежение третьим шагом, рефакторинг. Это приводит к тому, что код будет “грязным” (но по крайней мере, будут тесты).
Behaviour Driven Development BDD
BDD (Behaviour Driven Development) или разработка на основе поведения, появилось в процессе эволюции unit-тестирования и разработана Дэном Нортом (Dan North) в 2006г. Как утверждает сам автор, методология должна помочь людям изучить TDD. Она появилось из agile практик и предназначена сделать их более доступными и эффективными для команд-новичков в Agile. Со временем, BDD стало охватывать более широкую картину agile-анализа и автоматическое приемочное тестирование.
Это привело к тому, что сами тесты стали переименовывать в поведение (спецификации), что позволило сфокусироваться на том, что объекту нужно сделать. Таким образом, разработчики стали создавать для себя документацию и записывать названия тестов в виде предложений. Они обнаружили, что созданная документация, стала доступна бизнесу, разработчикам и тестерам.
Считается, что разработка на основе поведения одно из ответвлений Mock-стилей (или Solitary-тест), т.е. тесты преимущественно строятся с использованием дублей.
Позднее, появился стиль написания тестов Given-When-Then, или, как его стали называть, спецификация поведения системы. Идея заключается в том, чтобы разбить написание тестового сценария на три раздела:
- Дано (Given) — состояние, до того, как вы начнете описывать поведение. можно рассматривать как предварительное условие теста.
- Когда (When) — поведение, которое вы описываете.
- Тогда (Then) — изменения, которые вы ожидаете от поведения
Пример:
1Описание: Пользователь продает акции.2Сценарий: Пользователь запрашивает продажу до закрытия торгов3Дано (Given): У меня есть 100 акций MSFT и 150 акций APPL и время до закрытия торгов.4Когда (When): Я прошу продать 20 акций MSFT5Тогда (Then): У меня должно остаться 80 акций MSFT и 150 акций APPL и заявка на продажу 20 акций должна быть выполнена.
Методология BDD с точки зрения программистов, как утверждает сам ее автор (BDD IS LIKE TDD IF…), не отличается от TDD. Там используются все те же правила, что и в TDD: тест, код, рефакторинг. Отличие заключается в том, что BDD охватывает более широкую публику. Спецификации становятся доступными не только программистам, но и людям, не разбирающимся в коде, но имеющим отношение к разработке ПО. Таким образом, в процесс создания тестов подключается вся команда: аналитики, тестеры, менеджеры.
Пирамида тестирования Майка Кона
Это упрощенный вариант пирамиды. Unit — модульные тесты, применяемые в различных слоях приложения, тестирующие наименьшую делимую логику приложения: например, класс, но чаще всего — метод. Эти тесты обычно стараются по максимуму изолировать от внешней логики, то есть создать иллюзию того, что остальная часть приложения работает в стандартном режиме.
Данных тестов всегда должно быть много (больше, чем остальных видов), так как они тестируют маленькие кусочки и весьма легковесные, не кушающие много ресурсов (под ресурсами я имею виду оперативную память и время).
Integration — интеграционное тестирование. Оно проверяет более крупные кусочки системы, то есть это либо объединение нескольких кусочков логики (несколько методов или классов), либо корректность работы с внешним компонентом. Этих тестов как правило меньше, чем Unit, так как они тяжеловеснее.
Как пример интеграционных тестов можно рассмотреть соединение с базой данных и проверку правильной отработки методов, работающих с ней.
E2E(End-to-End) — тесты, которые проверяют работу пользовательского интерфейса. Они затрагивают логику на всех уровнях приложения, из-за чего их еще называют сквозными. Их как правило в разы меньше, так они наиболее тяжеловесны и должны проверять самые необходимые (используемые) пути.
На рисунке выше мы видим соотношение площадей разных частей треугольника: примерно такая же пропорция сохраняется в количестве этих тестов в реальной работе.
Ключевые понятия юнит-тестирования
Покрытие тестов (Code Coverage) — одна из главных оценок качества тестирования приложения. Это процент кода, который был покрыт тестами (0-100%).
Для оценки покрытия тестами обычно используют дополнительные инструменты: JaCoCo, Cobertura и т.д.
Тестовый сценарий (Test Case) — сценарий, описывающий шаги, конкретные условия и параметры, необходимые для проверки реализации тестируемого кода.
Фикстуры (Fixture) — состояние среды тестирования, которое необходимо для успешного выполнения испытуемого метода. Это заранее заданный набор объектов и их поведения в используемых условиях.
Этапы тестирования
Тест состоит из трёх этапов:
- Задание тестируемых данных (фикстур).
- Использование тестируемого кода (вызов тестируемого метода).
- Проверка результатов и сверка с ожидаемыми.
Чтобы обеспечить модульность теста, нужно нужно изолироваться от других слоев приложения. Сделать это можно помощью заглушек, моков и шпионов.
Мок (Mock) — объекты, которые настраиваются (например, специфично для каждого теста) и позволяют задать ожидания вызовы методов в виде ответов, которые мы планируем получить. Проверки соответствия ожиданиям проводятся через вызовы к Mock-объектам.
Заглушки (Stub) — обеспечивают жестко зашитый ответ на вызовы во время тестирования.
Также они могут сохранять в себе информацию о вызове (например, параметры или количество этих вызовов). Такие иногда называют своим термином — шпион (Spy).
Иногда термины stubs и mock путают: разница в том, что стаб ничего не проверяет, а лишь имитирует заданное состояние. А мок — это объект, у которого есть ожидания. Например, что данный метод класса должен быть вызван определенное число раз. Иными словами, ваш тест никогда не сломается из-за «стаба», а вот из-за мока может.
Среды тестирования
Для JavaScript доступно несколько сред тестирования (фреймворков). Самые популярные из них — Jest, Mocha, Jasmine и др .
Мы будет использовать Jest
Jest
Jest — это test runner, то есть библиотека JavaScript для создания, запуска и структурирования тестов. Jest распространяется в виде пакета NPM, вы можете установить его в любом проекте JavaScript.
Установка
Для установки Jest в ваш проект выполните:
1npm install --save-dev jest
После установки можете обновить секцию scripts вашего package.json:
1{2"scripts": {3 "test": "jest"4 }5}
С помощью такого простого вызова мы уже можем запустить наши тесты (на самом деле jest потребует существование хотя бы одного теста).
Также можно установить глобально (но так делать я бы не рекомендовал, так как по мне глобальная установка модулей является плохой практикой):
1npm install jest --global
После этого вы можете использовать jest непосредственно из командной строки.
При помощи вызова команды jest --init
в корне проекта, ответив на несколько вопросов, вы получите файл с настройками jest.config.js
.
Или можно добавить конфигурацию прямиком в ваш package.json
. Для этого добавьте в корень json ключ «jest» и в соответствующем ему объекте можете добавлять необходимые вам настройки. Сами опции мы разберем позже.
На данном этапе в этом нет необходимости, поскольку jest
можно использовать «сходу», без дополнительных конфигураций.
Первый тест
Файлы с расширением *.test.js
или *.spec.js
будут запущены и обработаны jest
по умалчанию.
1test('My first test', () => {2 expect(Math.max(1, 5, 10)).toBe(10);3});
Если мы "сломаем" на тест и запстим повторно
1test('My first test', () => {2 expect(Math.max(1, 5, 10)).toBe(5);3});
Как мы видим, теперь наш тест не проходит проверки. Jest отображает подробную информацию о том, где возникла проблема, какой был ожидаемый результат, и что мы получили вместо него.
Теперь давайте разберём код самого теста. Функция test
используется для создания нового теста. Она принимает три аргумента (в примере мы использовали вызов с двумя аргументами).
Первый — строка с названием теста, его jest
отобразит в отчете.
Второй — функция, которая содержит логику нашего теста.
Также можно использовать 3-й аргумент — таймаут. Он является не обязательным, а его значение по умолчанию составляет 5 секунд. Задаётся в миллисекундах.
Этот параметр необходим когда мы работаем с асинхронным кодом и возвращаем из функции теста промис.
Он указывает как долго jest должен ждать разрешения промиса.
По истечению этого времени, если промис не был разрешен — jest будет считать тест не пройденным.
Также вместо test()
можно использовать it()
. Разницы между такими вызовами нету.
it()
это просто алиас на функциюtest()
.
Внутри функции test
мы сначала вызываем expect()
. Ему мы передаем значение, которое хотим проверить. В нашем случае, это результат вызова Math.max(1, 5, 10). expect()
возвращает объект «обертку», у которой есть ряд методов для сопоставления полученного значения с ожидаемым. Один из таких методов мы и использовали — toBe
.
Давайте разберем основные из этих методов:
toBe()
— подходит, если нам надо сравнивать примитивные значения или является ли переданное значение ссылкой на тот же объект, что указан как ожидаемое значение. Сравниваются значения при помощи Object.is()
. В отличие от ===
это дает возможность отличать 0
от -0
, проверить равенство NaN
c NaN
.
toEqual()
— подойдёт, если нам необходимо сравнить структуру более сложных типов. Он сравнит все поля переданного объекта с ожидаемым. Проверит каждый элемент массива. И сделает это рекурсивно по всей вложенности.
1test('toEqual with objects', () => {2 expect({ foo: 'foo', subObject: { baz: 'baz' } })3 .toEqual({ foo: 'foo', subObject: { baz: 'baz' } }); // Ок4 expect({ foo: 'foo', subObject: { num: 0 } })5 .toEqual({ foo: 'foo', subObject: { baz: 'baz' } }); // А вот так ошибка.6});78test('toEqual with arrays', () => {9 expect([11, 19, 5]).toEqual([11, 19, 5]); // Ок10 expect([11, 19, 5]).toEqual([11, 19]); // Ошибка11});
toContain()
— проверят содержит массив или итерируемый объект значение. Для сравнения используется оператор ===
.
1const arr = ['apple', 'orange', 'banana'];2expect(arr).toContain('banana');3expect(new Set(arr)).toContain('banana');4expect('apple, orange, banana').toContain('banana');
toContainEqual()
— проверяет или содержит массив элемент с ожидаемой структурой.
1expect([{a: 1}, {b: 2}]).toContainEqual({a: 1});
toHaveLength()
— проверяет или свойство length
у объекта соответствует ожидаемому.
1expect([1, 2, 3, 4]).toHaveLength(4);2expect('foo').toHaveLength(3);3expect({ length: 1 }).toHaveLength(1);
toBeNull()
— проверяет на равенство с null
.
toBeUndefined()
— проверяет на равенство с undefined
.
toBeDefined()
— противоположность toBeUndefined. Проверяет или значение !== undefined
.
toBeTruthy()
— проверяет или в булевом контексте значение соответствует true. Тоесть любые значения кроме false
, null
, undefined
, 0
, NaN
и пустых строк.
toBeFalsy()
— противоположность toBeTruthy()
. Проверяет или в булевом контексте значение соответствует false
.
toBeGreaterThan()
и toBeGreaterThanOrEqual()
— первый метод проверяет или переданное числовое значение больше, чем ожидаемое >
, второй проверяет больше или равно ожидаемому >=
.
toBeLessThan()
и toBeLessThanOrEqual()
— противоположность toBeGreaterThan()
и toBeGreaterThanOrEqual()
toBeCloseTo()
— удобно использовать для чисел с плавающей запятой, когда вам не важна точность и вы не хотите, чтобы тест зависел от незначительной разницы в дроби. Вторым аргументом можно передать до какого знака после запятой необходима точность при сравнении.
1const num = 0.1 + 0.2; // 0.300000000000000042expect(num).toBeCloseTo(0.3);3expect(Math.PI).toBeCloseTo(3.14, 2);
toMatch()
— проверяет соответствие строки регулярному выражению.
1expect('Banana').toMatch(/Ba/);
toThrow()
— используется в случаях, когда надо проверить исключение. Можно проверить как сам факт ошибки, так и проверить на выброс исключения определенного класса, либо по сообщению ошибки, либо по соответствию сообщения регулярному выражению.
1function funcWithError() {2 throw new Error('some error');3}4expect(funcWithError).toThrow();5expect(funcWithError).toThrow(Error);6expect(funcWithError).toThrow('some error');7expect(funcWithError).toThrow(/some/);
not
— это свойство позволяет сделать проверки на НЕравенство. Оно предоставляет объект, который имеет все методы перечисленные выше, но работать они будут наоборот.
1expect(true).not.toBe(false);2expect({ foo: 'bar' }).not.toEqual({});34function funcWithoutError() {}5expect(funcWithoutError).not.toThrow();
Давайте напишем пару простых тестов.
1const area = (radius) => Math.PI * radius ** 2;2const circumference = (radius) => 2 * Math.PI * radius;345test('Circle area', () => {6 expect(circle.area(5)).toBeCloseTo(78.54);7 expect(circle.area()).toBeNaN();8});910test('Circumference', () => {11 expect(circle.circumference(11)).toBeCloseTo(69.1, 1);12 expect(circle.circumference()).toBeNaN();13});
В этих тестах мы проверили результат работы 2-х методов — area
и circumference
. При помощи метода toBeCloseTo
мы сверились с ожидаемым результатом. В первом случае мы проверили или вычисляемая площадь круга с радиусом 5
приблизительно равна 78.54
, при этом разница с полученым значением (оно составит 78.53981633974483
) не большая и тест будет засчитан.
Во втором мы указали, что нас интересует проверка с точностью до 1
знака после запятой. Также мы вызвали наши методы без аргументов и проверили результат с помощью toBeNaN
. Поскольку результат их выполнения будет NaN
, то и тесты будут пройдены успешно.
Разберём ещё один пример. Создадим функцию, которая будет фильтровать массив продуктов по цене:
1const byPriceRange = (products, min, max) =>2 products.filter(item => item.price >= min && item.price <= max);34const products = [5 { name: 'onion', price: 12 },6 { name: 'tomato', price: 26 },7 { name: 'banana', price: 29 },8 { name: 'orange', price: 38 }9];1011test('Test product filter by range', () => {12 const FROM = 15;13 const TO = 30;14 const filteredProducts = byPriceRange(products, FROM, TO);1516 expect(filteredProducts).toHaveLength(2);17 expect(filteredProducts).toContainEqual({ name: 'tomato', price: 26 });18 expect(filteredProducts).toEqual([{ name: 'tomato', price: 26 }, { name: 'banana', price: 29 }]);19 expect(filteredProducts[0].price).toBeGreaterThanOrEqual(FROM);20 expect(filteredProducts[1].price).toBeLessThanOrEqual(TO);21 expect(filteredProducts).not.toContainEqual({ name: 'orange', price: 38 });22});
В этом тесте мы проверям результат работы функии byRangePrice
. Сначала мы проверили соответствие длины полученого массива ожидаемой — 2
. Следующая проверка требует, чтобы в массиве находился элемент — { name: 'tomato', price: 26 }
. Объект в массиве и объект переданный toContainEqual
— это два разных объекта, а не ссылка на один и тот же. Но toContainEqual
сверит каждое свойство. Так как оба объекта идентичные — проверка пройдет успешно.
Далее мы используем toEqual для провеки структуры всего массива и его элементов. Методы toBeGreaterThanOrEqual
и toBeLessThanOrEqual
помогут нам проверить price
первого и второго элемента массива. И, наконец, вызов not.toContainEqual
сделает проверку, не содержится ли в массиве элемент — { name: 'orange', price: 38 }
, которого по условию там быть не должно.
В данных примерах мы написали несколько простых тестов используя функции проверки описанные выше. В следующих частях мы разберём работу с асинхронным кодом, функции jest
которые не затрагивались в этой части туториала, поговорим о его настройке и многое другое.
Expect
Если бы мы сами захотели бы написать функцию expect
то она выглядела бы так
1const sum = (a, b) => a - b2const subtract = (a, b) => a - b34let result, expected5result = sum(3, 7)6expected = 1078expect(result).toBe(expected)910result = subtract(7, 3)11expected = 412expect(result).toBe(expected)1314function expect(actual) {15 return {16 toBe(expected) {17 if (actual !== expected) {18 throw new Error(`${actual} is not equal to ${expected}`)19 }20 }21 }22}
Mock-функции
Мок-функции позволяют тестировать связи между кодом, стирая фактическую реализацию функции, фиксируя вызовы функции (и параметры, переданные в этих вызовах), захватывая экземпляры функций-конструкторов при их создании с помощью new и разрешая конфигурацию во время тестирования. возвращаемых значений.
Использование фиктивной функции
Представим, что мы тестируем реализацию функции forEach
, которая вызывает обратный вызов для каждого элемента в предоставленном массиве.
1function forEach(items, callback) {2 for (let index = 0; index < items.length; index++) {3 callback(items[index]);4 }5}
Чтобы протестировать эту функцию, мы можем использовать мок-функцию, и посмотреть на состояние мока чтобы убедиться, что функция была вызвана как ожидалось.
1const mockCallback = jest.fn(x => 42 + x);2forEach([0, 1], mockCallback);34// The mock function is called twice5expect(mockCallback.mock.calls.length).toBe(2);67// The first argument of the first call to the function was 08expect(mockCallback.mock.calls[0][0]).toBe(0);910// The first argument of the second call to the function was 111expect(mockCallback.mock.calls[1][0]).toBe(1);1213// The return value of the first call to the function was 4214expect(mockCallback.mock.results[0].value).toBe(42);
.mock свойство
У всех мок-функций есть особое свойство .mock
, где хранятся данные о том как функция была вызвана и что она вернула. Свойство .mock
также отслеживает значение this
для каждого вызова, так что как правило это можно посмотреть:
1const myMock = jest.fn();23const a = new myMock();4const b = {};5const bound = myMock.bind(b);6bound();78console.log(myMock.mock.instances);9// > [ <a>, <b> ]
Эти свойства мока очень полезны в тестах чтобы указывать как эти функции вызываются, наследуются, или что они возвращают:
1expect(someMockFunction.mock.calls.length).toBe(1);23expect(someMockFunction.mock.calls[0][0]).toBe('first arg');45expect(someMockFunction.mock.calls[0][1]).toBe('second arg');67expect(someMockFunction.mock.results[0].value).toBe('return value');89expect(someMockFunction.mock.instances.length).toBe(2);1011expect(someMockFunction.mock.instances[0].name).toEqual('test');
Мок-функции также могут использоваться для внедрения тестовых значений в ваш код во время тестирования:
1const myMock = jest.fn();2console.log(myMock());3// > undefined45myMock.mockReturnValueOnce(10).mockReturnValueOnce('x').mockReturnValue(true);67console.log(myMock(), myMock(), myMock(), myMock());8// > 10, 'x', true, true
Мок-функции также очень эффективны в коде, использующем функциональный стиль передачи продолжения. Код, написанный в этом стиле, помогает избежать необходимости в сложных заглушках, воссоздающих поведение реального компонента, за которым они стоят, в пользу введения значений непосредственно в тест прямо перед их использованием.
1const filterTestFn = jest.fn();23// Make the mock return `true` for the first call,4// and `false` for the second call5filterTestFn.mockReturnValueOnce(true).mockReturnValueOnce(false);67const result = [11, 12].filter(num => filterTestFn(num));89console.log(result);10// > [11]11console.log(filterTestFn.mock.calls[0][0]); // 1112console.log(filterTestFn.mock.calls[1][0]); // 12
Тем не менее, есть случаи, когда полезно выйти за рамки возможности указывать возвращаемые значения и полностью заменить реализацию фиктивной функции. Это можно сделать с помощью jest.fn
или метода mockImplementationOnce
для фиктивных функций.
1const myMockFn = jest.fn(cb => cb(null, true));23myMockFn((err, val) => console.log(val));4// > true
Метод mockImplementation
полезен, когда вам нужно определить реализацию по умолчанию фиктивной функции, которая создается из другого модуля:
1const foo = jest.fn()2foo.mockImplementation(() => 42);3foo();
Когда вам нужно воссоздать сложное поведение фиктивной функции, при котором несколько вызовов функций приводят к разным результатам, используйте метод mockImplementationOnce
:
1const myMockFn = jest2 .fn()3 .mockImplementationOnce(cb => cb(null, true))4 .mockImplementationOnce(cb => cb(null, false));56myMockFn((err, val) => console.log(val));7// > true89myMockFn((err, val) => console.log(val));10// > false
Когда mock функция исчерпывает реализации, определенные с помощью mockImplementationOnce
, она выполнит реализацию по умолчанию, установленную с помощью jest.fn
(если она определена):
1const myMockFn = jest2 .fn(() => 'default')3 .mockImplementationOnce(() => 'first call')4 .mockImplementationOnce(() => 'second call');56console.log(myMockFn(), myMockFn(), myMockFn(), myMockFn());7// > 'first call', 'second call', 'default', 'default'
Для случаев, когда у нас есть методы, которые обычно связаны цепочкой (и, следовательно, всегда нужно возвращать это), у нас есть сладкий API, чтобы упростить это в виде функции .mockReturnThis()
, которая также находится во всех моков:
1const myObj = {2 myMethod: jest.fn().mockReturnThis(),3};45// is the same as67const otherObj = {8 myMethod: jest.fn(function () {9 return this;10 }),11};
При желании вы можете указать имя для ваших фиктивных функций, которое будет отображаться вместо «jest.fn()
» в выводе ошибок теста. Используйте это, если вы хотите иметь возможность быстро идентифицировать фиктивную функцию, сообщающую об ошибке в вашем тестовом выводе.
1const myMockFn = jest2 .fn()3 .mockReturnValue('default')4 .mockImplementation(scalar => 42 + scalar)5 .mockName('add42');
Наконец, чтобы упростить утверждение того, как были вызваны фиктивные функции, мы добавили для вас несколько настраиваемых функций сопоставления:
1const mockFunc = jest.fn()2//Мок-функция вызывалась хотя бы один раз3expect(mockFunc).toHaveBeenCalled();45// Мок-функция вызывалась хотя бы один раз с указанными аргументами.6expect(mockFunc).toHaveBeenCalledWith(arg1, arg2);78// Последний вызов фиктивной функции был вызван с указанными аргументами9expect(mockFunc).toHaveBeenLastCalledWith(arg1, arg2);1011// Все звонки и название макета записываются в виде снимка12expect(mockFunc).toMatchSnapshot();
Эти сопоставители являются сахаром для обычных форм проверки свойства .mock
. Вы всегда можете сделать это вручную самостоятельно, если это вам больше по вкусу или вам нужно сделать что-то более конкретное:
1const mockFunc = jest.fn()2// Мок-функция вызывалась хотя бы один раз3expect(mockFunc.mock.calls.length).toBeGreaterThan(0);45// Мок-функция была вызвана хотя бы один раз с указанными аргументами6expect(mockFunc.mock.calls).toContainEqual([arg1, arg2]);78// Последний вызов фиктивной функции был вызван с указанными аргументами9expect(mockFunc.mock.calls[mockFunc.mock.calls.length - 1]).toEqual([10 arg1,11 arg2,12]);1314// Первый аргумент последнего вызова фиктивной функции был `42`15// (обратите внимание, что для этого конкретного утверждения нет сахарного помощника)16expect(mockFunc.mock.calls[mockFunc.mock.calls.length - 1][0]).toBe(42);1718// Снимок проверит, что макет был вызван одинаковое количество раз,19// в том же порядке, с теми же аргументами. Это также будет утверждаться по имени.20expect(mockFunc.mock.calls).toEqual([[arg1, arg2]]);21expect(mockFunc.getMockName()).toBe('a mock name');