«Если рабочий хочет хорошо выполнять свою работу, он должен сначала заточить свои инструменты» — Конфуций, «Аналитики Конфуция. Лу Лингун»
титульная страница > программирование > Переопределить функции в отдельных тестах с помощью Jest

Переопределить функции в отдельных тестах с помощью Jest

Опубликовано 2 ноября 2024 г.
Просматривать:662

Override functions in individual tests using Jest

Иногда вам хочется имитировать функцию в одних тестах, но не в других. Иногда вам нужно предоставить разные макеты для разных тестов. Jest усложняет задачу: его поведение по умолчанию заключается в переопределении функции пакета для всего тестового файла, а не только для одного теста. Это кажется странным, если вы использовали гибкие инструменты, такие как @patch Python или сервис-контейнер Laravel.

В этом посте показано, как имитировать функции для отдельных тестов, а затем возвращаться к исходной реализации, если макет не был предоставлен. Примеры будут приведены как для модулей CommonJS, так и для ES. Методы, продемонстрированные в этом посте, будут работать как для собственных модулей, так и для сторонних пакетов.

CommonJS против модулей ES

Поскольку в этом посте мы рассмотрим несколько модульных систем, важно понимать, что они из себя представляют.

CommonJS (сокращенно CJS) — это система модулей в Node.js. Он экспортирует функции с помощью модуля.exports и импортирует функции с помощью require():

// CommonJS export 

function greet() {
  return "Hello, world!";
}

module.exports = { greet };
// CommonJS import

const getUsersList = require('./greet');

Модули ES (сокращенно ESM) — это система модулей, используемая браузером. Он экспортирует функции, используя ключевое слово экспорта, и импортирует функции, используя ключевое слово импорта:

// ES module export

export default function greet() {
  return "Hello, world!";
}
// ES module import

import { greet } from "./greet";

На момент написания этой статьи большинство веб-разработчиков JavaScript используют ES-модули, и многие серверные JS-разработчики также используют их. Однако CommonJS по-прежнему используется по умолчанию для Node. Независимо от того, какую систему вы используете, стоит прочитать всю статью, чтобы узнать о системе насмешек Jest.

Издевательство над одной экспортированной функцией с помощью CommonJS

Обычно файл CommonJS экспортирует свои модули с использованием синтаксиса объекта, как показано ниже:

// CommonJS export 

function greet() {
  return "Hello, world!";
}

module.exports = { greet: greet };

Однако можно экспортировать функцию и отдельно:

// CommonJS export 

function greet() {
  return "Hello, world!";
}

module.exports = greet;

Я бы не рекомендовал делать это в вашем собственном коде: экспорт объекта доставит вам меньше головной боли при разработке приложения. Однако это настолько распространено, что стоит обсудить, как имитировать пустую экспортированную функцию в CommonJS, а затем вернуться к оригиналу, если тест не предоставляет собственную реализацию.

Предположим, у нас есть следующий файл CommonJS, который мы хотели бы имитировать во время тестов:

// cjsFunction.js

function testFunc() {
  return "original";
}

module.exports = testFunc;

Мы могли бы высмеять это в наших тестах, используя следующий код:

const testFunc = require("./cjsFunction");

jest.mock("./cjsFunction");

beforeEach(() => {
  testFunc.mockImplementation(jest.requireActual("./cjsFunction"));
});

it("can override the implementation for a single test", () => {
  testFunc.mockImplementation(() => "mock implementation");

  expect(testFunc()).toBe("mock implementation");
  expect(testFunc.mock.calls).toHaveLength(1);
});

it("can override the return value for a single test", () => {
  testFunc.mockReturnValue("mock return value");

  expect(testFunc()).toBe("mock return value");
  expect(testFunc.mock.calls).toHaveLength(1);
});

it("returns the original implementation when no overrides exist", () => {
  expect(testFunc()).toBe("original");
  expect(testFunc.mock.calls).toHaveLength(1);
});

Как это работает

Когда мы вызываем jest.mock("./cjsFunction"), это заменяет модуль (файл и весь его экспорт) автоматическим макетом (документация). Когда вызывается автоматический макет, он возвращает неопределенное значение. Однако он предоставит методы для переопределения реализации макета, возвращаемого значения и многого другого. Вы можете увидеть все свойства и методы, которые он предоставляет, в документации Jest Mock Functions.

Мы можем использовать метод mockImplementation() макета, чтобы автоматически установить реализацию макета в соответствии с реализацией исходного модуля. Jest предоставляет метод jest.requireActual(), который всегда загружает исходный модуль, даже если в данный момент над ним издеваются.

Реализации макета и возвращаемые значения автоматически очищаются после каждого теста, поэтому мы можем передать функцию обратного вызова в функцию beforeEach() Jest, которая устанавливает реализацию макета в исходную реализацию перед каждым тестом. Тогда любые тесты, которые хотят предоставить собственное возвращаемое значение или реализацию, могут сделать это вручную в теле теста.

Издевательство над CommonJS при экспорте объекта

Предположим, что приведенный выше код экспортировал объект вместо одной функции:

// cjsModule.js

function testFunc() {
  return "original";
}

module.exports = {
  testFunc: testFunc,
};

Тогда наши тесты будут выглядеть так:

const cjsModule = require("./cjsModule");

afterEach(() => {
  jest.restoreAllMocks();
});

it("can override the implementation for a single test", () => {
  jest
    .spyOn(cjsModule, "testFunc")
    .mockImplementation(() => "mock implementation");

  expect(cjsModule.testFunc()).toBe("mock implementation");
  expect(cjsModule.testFunc.mock.calls).toHaveLength(1);
});

it("can override the return value for a single test", () => {
  jest.spyOn(cjsModule, "testFunc").mockReturnValue("mock return value");

  expect(cjsModule.testFunc()).toBe("mock return value");
  expect(cjsModule.testFunc.mock.calls).toHaveLength(1);
});

it("returns the original implementation when no overrides exist", () => {
  expect(cjsModule.testFunc()).toBe("original");
});

it("can spy on calls while keeping the original implementation", () => {
  jest.spyOn(cjsModule, "testFunc");

  expect(cjsModule.testFunc()).toBe("original");
  expect(cjsModule.testFunc.mock.calls).toHaveLength(1);
});

Как это работает

Метод jest.spyOn() позволяет Jest записывать вызовы метода объекта и предоставлять собственную замену. Это только работает с объектами, и мы можем его использовать, поскольку наш модуль экспортирует объект, содержащий нашу функцию.

Метод spyOn() является макетом, поэтому его состояние необходимо сбросить. В документации Jest spyOn() рекомендуется сбрасывать состояние с помощью jest.restoreAllMocks() в обратном вызове afterEach(), что мы и сделали выше. Если бы мы этого не сделали, макет вернул бы неопределенное значение в следующем тесте после вызова spyOn().

Имитация модулей ES

Модули ES могут иметь экспорт по умолчанию и именованный:

// esmModule.js

export default function () {
  return "original default";
}

export function named() {
  return "original named";
}

Вот как будут выглядеть тесты для файла выше:

import * as esmModule from "./esmModule";

afterEach(() => {
  jest.restoreAllMocks();
});

it("can override the implementation for a single test", () => {
  jest
    .spyOn(esmModule, "default")
    .mockImplementation(() => "mock implementation default");
  jest
    .spyOn(esmModule, "named")
    .mockImplementation(() => "mock implementation named");

  expect(esmModule.default()).toBe("mock implementation default");
  expect(esmModule.named()).toBe("mock implementation named");

  expect(esmModule.default.mock.calls).toHaveLength(1);
  expect(esmModule.named.mock.calls).toHaveLength(1);
});

it("can override the return value for a single test", () => {
  jest.spyOn(esmModule, "default").mockReturnValue("mock return value default");
  jest.spyOn(esmModule, "named").mockReturnValue("mock return value named");

  expect(esmModule.default()).toBe("mock return value default");
  expect(esmModule.named()).toBe("mock return value named");

  expect(esmModule.default.mock.calls).toHaveLength(1);
  expect(esmModule.named.mock.calls).toHaveLength(1);
});

it("returns the original implementation when no overrides exist", () => {
  expect(esmModule.default()).toBe("original default");
  expect(esmModule.named()).toBe("original named");
});

Как это работает

Это выглядит почти так же, как и предыдущий пример CommonJS, с парой ключевых отличий.

Сначала мы импортируем наш модуль как импорт пространства имен.

import * as esmModule from "./esmModule";

Затем, когда мы хотим отслеживать экспорт по умолчанию, мы используем «default»:

  jest
    .spyOn(esmModule, "default")
    .mockImplementation(() => "mock implementation default");

Устранение неполадок импорта модуля ES

Иногда при попытке вызвать jest.spyOn() из стороннего пакета вы получаете сообщение об ошибке, подобное приведенному ниже:

    TypeError: Cannot redefine property: useNavigate
        at Function.defineProperty ()

Когда вы столкнетесь с этой ошибкой, вам нужно будет имитировать пакет, вызывающий проблему:

import * as reactRouterDOM from "react-router-dom";

// ADD THIS:
jest.mock("react-router-dom", () => {
  const originalModule = jest.requireActual("react-router-dom");

  return {
    __esModule: true,
    ...originalModule,
  };
});

afterEach(() => {
  jest.restoreAllMocks();
});

Этот код заменяет модуль макетом модуля Jest ES, который содержит все исходные свойства модуля с использованием фабричного параметра jest.mocks. Свойство __esModule требуется всякий раз, когда используется фабричный параметр в jest.mock для имитации модуля ES (документация).

При желании вы также можете заменить отдельную функцию в заводском параметре. Например, React Router выдаст ошибку, если потребитель вызовет useNavigate() вне контекста Router, поэтому мы можем использовать jest.mock() для замены этой функции во всем тестовом файле, если захотим:

jest.mock("react-router-dom", () => {
  const originalModule = jest.requireActual("react-router-dom");

  return {
    __esModule: true,
    ...originalModule,

    // Dummy that does nothing.
    useNavigate() {
      return function navigate(_location) {
        return;
      };
    },
  };
});

Подведение итогов

Надеюсь, эта информация будет полезна при написании собственных тестов. Не каждое приложение выиграет от возможности вернуться к реализации по умолчанию, если в самом тесте реализация не указана. Действительно, многие приложения захотят использовать один и тот же макет для всего файла тестирования. Однако методы, показанные в этом посте, дадут вам более детальный контроль над издевательством.

Дайте мне знать, если я что-то пропустил или есть что-то, что я не включил в этот пост, и это должно быть здесь.

Заявление о выпуске Эта статья воспроизведена по адресу: https://dev.to/tylerlwsmith/override-functions-in-individual-tests-using-jest-dp5?1. Если есть какие-либо нарушения, свяжитесь с [email protected], чтобы удалить их.
Последний учебник Более>

Изучайте китайский

Отказ от ответственности: Все предоставленные ресурсы частично взяты из Интернета. В случае нарушения ваших авторских прав или других прав и интересов, пожалуйста, объясните подробные причины и предоставьте доказательства авторских прав или прав и интересов, а затем отправьте их по электронной почте: [email protected]. Мы сделаем это за вас как можно скорее.

Copyright© 2022 湘ICP备2022001581号-3