„Wenn ein Arbeiter seine Arbeit gut machen will, muss er zuerst seine Werkzeuge schärfen.“ – Konfuzius, „Die Gespräche des Konfuzius. Lu Linggong“
Titelseite > Programmierung > Überschreiben Sie Funktionen in einzelnen Tests mit Jest

Überschreiben Sie Funktionen in einzelnen Tests mit Jest

Veröffentlicht am 02.11.2024
Durchsuche:271

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 Drittanbieterpakete.

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.

Angenommen, 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, gibt es undefiniert zurück. Es stellt jedoch Methoden zum Überschreiben der Mock-Implementierung, des Rückgabewerts und mehr bereit. Sie können alle bereitgestellten Eigenschaften und Methoden in der Dokumentation zu Jest Mock Functions sehen.

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 Status 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 getan hätten, 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");

Fehlerbehebung bei ES-Modulimporten

Manchmal erhalten Sie beim Versuch, jest.spyOn() mit einem Paket eines Drittanbieters aufzurufen, eine Fehlermeldung wie die folgende:

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

Wenn dieser Fehler auftritt, müssen Sie das Paket verspotten, das das Problem verursacht:

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

Dieser Code ersetzt das Modul durch einen Jest ES-Modul-Mock, der alle ursprünglichen Eigenschaften des Moduls unter Verwendung des Factory-Parameters von jest.mocks enthält. Die __esModule-Eigenschaft ist immer dann erforderlich, wenn ein Factory-Parameter in jest.mock verwendet wird, um ein ES-Modul (Dokumente) zu verspotten.

Wenn Sie möchten, können Sie auch eine einzelne Funktion im Factory-Parameter ersetzen. React Router gibt beispielsweise einen Fehler aus, wenn ein Verbraucher useNavigate() außerhalb eines Router-Kontexts aufruft. Daher könnten wir jest.mock() verwenden, um diese Funktion bei Bedarf in der gesamten Testdatei zu ersetzen:

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

Einpacken

Ich hoffe, dass diese Informationen für Sie hilfreich sind, wenn Sie Ihre eigenen Tests schreiben. Nicht jede App profitiert davon, auf die Standardimplementierung zurückgreifen zu können, wenn in einem Test selbst keine Implementierung bereitgestellt wird. Tatsächlich möchten viele Apps denselben Mock für eine gesamte Testdatei verwenden. Die in diesem Beitrag gezeigten Techniken geben Ihnen jedoch eine detaillierte Kontrolle über Ihr Spott.

Lassen Sie mich wissen, wenn ich etwas verpasst habe oder wenn es etwas gibt, das ich nicht in diesen Beitrag aufgenommen habe und das hier sein sollte.

Freigabeerklärung Dieser Artikel ist abgedruckt unter: https://dev.to/tylerlwsmith/override-functions-in-individual-tests-using-jest-dp5?1 Bei Verstößen wenden Sie sich bitte an [email protected], um ihn zu löschen
Neuestes Tutorial Mehr>

Haftungsausschluss: Alle bereitgestellten Ressourcen stammen teilweise aus dem Internet. Wenn eine Verletzung Ihres Urheberrechts oder anderer Rechte und Interessen vorliegt, erläutern Sie bitte die detaillierten Gründe und legen Sie einen Nachweis des Urheberrechts oder Ihrer Rechte und Interessen vor und senden Sie ihn dann an die E-Mail-Adresse: [email protected] Wir werden die Angelegenheit so schnell wie möglich für Sie erledigen.

Copyright© 2022 湘ICP备2022001581号-3