Heim  >  Artikel  >  Web-Frontend  >  Überschreiben Sie Funktionen in einzelnen Tests mit Jest

Überschreiben Sie Funktionen in einzelnen Tests mit Jest

WBOY
WBOYOriginal
2024-08-05 22:43:02613Durchsuche

Override functions in individual tests using Jest

Manchmal möchten Sie eine Funktion in einigen Tests verspotten, in anderen jedoch nicht. Manchmal möchten Sie unterschiedliche Mocks für unterschiedliche Tests bereitstellen. Jest macht dies schwierig: Sein Standardverhalten besteht darin, die Funktion eines Pakets für eine ganze Testdatei zu überschreiben, nicht nur für einen einzelnen Test. Dies erscheint seltsam, wenn Sie flexible Tools wie Pythons @patch oder den Service-Container von Laravel verwendet haben.

In diesem Beitrag erfahren Sie, wie Sie Funktionen für einzelne Tests verspotten und dann auf die ursprüngliche Implementierung zurückgreifen, wenn kein Mock bereitgestellt wurde. Es werden Beispiele für CommonJS- und ES-Module gegeben. Die in diesem Beitrag demonstrierten Techniken funktionieren sowohl für Erstanbietermodule als auch für Pakete von Drittanbietern.

CommonJS vs. ES-Module

Da wir in diesem Beitrag mehrere Modulsysteme behandeln, ist es wichtig zu verstehen, was sie sind.

CommonJS (abgekürzt CJS) ist das Modulsystem in Node.js. Es exportiert Funktionen mit module.exports und importiert Funktionen mit require():

// CommonJS export 

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

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

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

ES-Module (abgekürzt ESM) ist das Modulsystem, das vom Browser verwendet wird. Es exportiert Funktionen mit dem Schlüsselwort export und importiert Funktionen mit dem Schlüsselwort import:

// ES module export

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

import { greet } from "./greet";

Die meisten Frontend-JavaScript-Entwickler verwenden zum Zeitpunkt des Verfassens dieses Beitrags ES-Module, und viele serverseitige JS-Entwickler verwenden sie ebenfalls. Allerdings ist CommonJS immer noch die Standardeinstellung für Node. Unabhängig davon, welches System Sie verwenden, lohnt es sich, den gesamten Artikel zu lesen, um mehr über das Spottsystem von Jest zu erfahren.

Verspotten einer einzelnen exportierten Funktion mit CommonJS

Normalerweise exportiert eine CommonJS-Datei ihre Module mithilfe der Objektsyntax, wie unten gezeigt:

// CommonJS export 

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

module.exports = { greet: greet };

Es ist jedoch auch möglich, eine Funktion einzeln zu exportieren:

// CommonJS export 

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

module.exports = greet;

Ich würde nicht unbedingt empfehlen, dies in Ihrem eigenen Code zu tun: Das Exportieren eines Objekts bereitet Ihnen weniger Kopfschmerzen bei der Entwicklung Ihrer Anwendung. Es kommt jedoch häufig genug vor, dass es sich lohnt, darüber zu diskutieren, wie man eine bloße exportierte Funktion in CommonJS nachahmt und dann auf das Original zurückgreift, wenn ein Test keine eigene Implementierung bereitstellt.

Nehmen wir an, wir haben die folgende CommonJS-Datei, die wir während der Tests verspotten möchten:

// cjsFunction.js

function testFunc() {
  return "original";
}

module.exports = testFunc;

Wir konnten es in unseren Tests mit dem folgenden Code nachahmen:

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);
});

Wie es funktioniert

Wenn wir jest.mock("./cjsFunction") aufrufen, ersetzt dies das Modul (die Datei und alle ihre Exporte) durch ein Auto-Mock (Dokumente). Wenn ein Auto-Mock aufgerufen wird, wird undefiniert zurückgegeben. Es stellt jedoch Methoden zum Überschreiben der Mock-Implementierung, des Rückgabewerts und mehr bereit. Alle bereitgestellten Eigenschaften und Methoden finden Sie in der Dokumentation zu Jest Mock Functions.

Wir können die Methode „mockImplementation()“ des Mocks verwenden, um die Implementierung des Mocks automatisch auf die Implementierung des Originalmoduls festzulegen. Jest bietet eine jest.requireActual()-Methode, die immer das Originalmodul lädt, auch wenn es gerade verspottet wird.

Mock-Implementierungen und Rückgabewerte werden nach jedem Test automatisch gelöscht, sodass wir eine Rückruffunktion an die beforeEach()-Funktion von Jest übergeben können, die die Implementierung des Mocks vor jedem Test auf die ursprüngliche Implementierung setzt. Dann können alle Tests, die ihren eigenen Rückgabewert oder ihre eigene Implementierung bereitstellen möchten, dies manuell im Testkörper tun.

Verspottung von CommonJS beim Exportieren eines Objekts

Angenommen, der obige Code hätte ein Objekt anstelle einer einzelnen Funktion exportiert:

// cjsModule.js

function testFunc() {
  return "original";
}

module.exports = {
  testFunc: testFunc,
};

Unsere Tests würden dann so aussehen:

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);
});

Wie es funktioniert

Die Methode jest.spyOn() ermöglicht es Jest, Aufrufe einer Methode für ein Objekt aufzuzeichnen und einen eigenen Ersatz bereitzustellen. Dies funktioniert nur bei Objekten und wir können es verwenden, da unser Modul ein Objekt exportiert, das unsere Funktion enthält.

Die spyOn()-Methode ist ein Schein, daher muss ihr Zustand zurückgesetzt werden. In der Jest spyOn()-Dokumentation wird empfohlen, den Status mit jest.restoreAllMocks() in einem afterEach()-Rückruf zurückzusetzen, was wir oben getan haben. Wenn wir dies nicht tun würden, würde der Mock im nächsten Test nach dem Aufruf von spyOn() undefiniert zurückgeben.

Verspottung von ES-Modulen

ES-Module können Standard- und benannte Exporte haben:

// esmModule.js

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

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

So würden die Tests für die obige Datei aussehen:

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");
});

Wie es funktioniert

Das sieht fast genauso aus wie das vorherige CommonJS-Beispiel, mit ein paar wesentlichen Unterschieden.

Zuerst importieren wir unser Modul als Namespace-Import.

import * as esmModule from "./esmModule";

Wenn wir dann den Standardexport ausspionieren wollen, verwenden wir „default“:

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

Troubleshooting ES module imports

Sometimes when trying to call jest.spyOn() with a third-party package, you'll get an error like the one below:

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

When you run into this error, you'll need to mock the package that is causing the issue:

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();
});

This code replaces the module with a Jest ES Module mock that contains all of the module's original properties using jest.mocks's factory parameter. The __esModule property is required whenever using a factory parameter in jest.mock to mock an ES module (docs).

If you wanted, you could also replace an individual function in the factory parameter. For example, React Router will throw an error if a consumer calls useNavigate() outside of a Router context, so we could use jest.mock() to replace that function throughout the whole test file if we desired:

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;
      };
    },
  };
});

Wrapping up

I hope this information is valuable as you write your own tests. Not every app will benefit from being able to fallback to the default implementation when no implementation is provided in a test itself. Indeed, many apps will want to use the same mock for a whole testing file. However, the techniques shown in this post will give you fine-grained control over your mocking.

Let me know if I missed something or if there's something that I didn't include in this post that should be here.

Das obige ist der detaillierte Inhalt vonÜberschreiben Sie Funktionen in einzelnen Tests mit Jest. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn