"Si un ouvrier veut bien faire son travail, il doit d'abord affûter ses outils." - Confucius, "Les Entretiens de Confucius. Lu Linggong"
Page de garde > La programmation > Remplacer les fonctions dans les tests individuels à l'aide de Jest

Remplacer les fonctions dans les tests individuels à l'aide de Jest

Publié le 2024-11-02
Parcourir:978

Override functions in individual tests using Jest

Parfois, vous souhaitez vous moquer d'une fonction dans certains tests mais pas dans d'autres. Parfois, vous souhaitez fournir différentes simulations à différents tests. Jest rend cela délicat : son comportement par défaut est de remplacer la fonction d'un package pour un fichier de test entier, pas seulement pour un seul test. Cela semble étrange si vous avez utilisé des outils flexibles comme @patch de Python ou le conteneur de services de Laravel.

Cet article vous montrera comment simuler des fonctions pour des tests individuels, puis revenir à l'implémentation d'origine si aucune simulation n'a été fournie. Des exemples seront donnés pour les modules CommonJS et ES. Les techniques démontrées dans cet article fonctionneront à la fois pour les modules propriétaires et les packages tiers.

Modules CommonJS et ES

Étant donné que nous aborderons plusieurs systèmes de modules dans cet article, il est important de comprendre de quoi il s'agit.

CommonJS (en abrégé CJS) est le système de modules de Node.js. Il exporte des fonctions à l'aide de module.exports et importe des fonctions à l'aide de require() :

// CommonJS export 

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

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

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

Modules ES (ESM en abrégé) est le système de modules utilisé par le navigateur. Il exporte des fonctions à l'aide du mot-clé export et importe des fonctions à l'aide du mot-clé import :

// ES module export

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

import { greet } from "./greet";

La plupart des développeurs JavaScript frontend utilisent des modules ES au moment de la rédaction de cet article, et de nombreux développeurs JS côté serveur les utilisent également. Cependant, CommonJS est toujours la valeur par défaut pour Node. Quel que soit le système que vous utilisez, il vaut la peine de lire l'intégralité de l'article pour en savoir plus sur le système moqueur de Jest.

Se moquer d'une seule fonction exportée avec CommonJS

En général, un fichier CommonJS exportera ses modules en utilisant la syntaxe d'objet, comme indiqué ci-dessous :

// CommonJS export 

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

module.exports = { greet: greet };

Cependant, il est également possible d'exporter une fonction seule :

// CommonJS export 

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

module.exports = greet;

Je ne recommanderais pas nécessairement de faire cela dans votre propre code : l'exportation d'un objet vous donnera moins de maux de tête lors du développement de votre application. Cependant, c'est assez courant pour qu'il vaut la peine de discuter de la façon de simuler une simple fonction exportée dans CommonJS, puis de revenir à l'original si un test ne fournit pas sa propre implémentation.

Disons que nous avons le fichier CommonJS suivant dont nous aimerions nous moquer lors des tests :

// cjsFunction.js

function testFunc() {
  return "original";
}

module.exports = testFunc;

Nous pourrions nous en moquer dans nos tests en utilisant le code suivant :

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

Comment ça marche

Lorsque nous appelons jest.mock("./cjsFunction"), cela remplace le module (le fichier et toutes ses exportations) par une simulation automatique (docs). Lorsqu'une simulation automatique est appelée, elle renvoie un élément indéfini. Cependant, il fournira des méthodes pour remplacer l'implémentation de la simulation, la valeur de retour, etc. Vous pouvez voir toutes les propriétés et méthodes qu'il fournit dans la documentation Jest Mock Functions.

Nous pouvons utiliser la méthode mockImplementation() de la simulation pour définir automatiquement l'implémentation de la simulation sur l'implémentation du module d'origine. Jest fournit une méthode jest.requireActual() qui chargera toujours le module d'origine, même s'il est actuellement simulé.

Les implémentations simulées et les valeurs de retour sont automatiquement effacées après chaque test, nous pouvons donc transmettre une fonction de rappel à la fonction beforeEach() de Jest qui définit l'implémentation de la simulation sur l'implémentation d'origine avant chaque test. Ensuite, tous les tests qui souhaitent fournir leur propre valeur de retour ou implémentation peuvent le faire manuellement dans le corps du test.

Se moquer de CommonJS lors de l'exportation d'un objet

Disons que le code ci-dessus avait exporté un objet au lieu d'une seule fonction :

// cjsModule.js

function testFunc() {
  return "original";
}

module.exports = {
  testFunc: testFunc,
};

Nos tests ressembleraient alors à ceci :

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

Comment ça marche

La méthode jest.spyOn() permet à Jest d'enregistrer les appels à une méthode sur un objet et de fournir son propre remplacement. Ceci uniquement fonctionne sur les objets, et nous pouvons l'utiliser car notre module exporte un objet qui contient notre fonction.

La méthode spyOn() est fictive, son état doit donc être réinitialisé. La documentation Jest spyOn() recommande de réinitialiser l'état en utilisant jest.restoreAllMocks() dans un rappel afterEach(), ce que nous avons fait ci-dessus. Si nous ne le faisions pas, la simulation renverrait undéfini lors du prochain test après l'appel de spyOn().

Modules ES moqueurs

Les modules ES peuvent avoir des exportations par défaut et nommées :

// esmModule.js

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

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

Voici à quoi ressembleraient les tests pour le fichier ci-dessus :

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

Comment ça marche

Cela ressemble presque à la même chose que l'exemple CommonJS précédent, avec quelques différences clés.

Tout d'abord, nous importons notre module en tant qu'importation d'espace de noms.

import * as esmModule from "./esmModule";

Puis quand on veut espionner l'export par défaut, on utilise "default":

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

Dépannage des importations de modules ES

Parfois, lorsque vous essayez d'appeler jest.spyOn() avec un package tiers, vous obtenez une erreur comme celle ci-dessous :

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

Lorsque vous rencontrez cette erreur, vous devrez vous moquer du package à l'origine du problème :

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

Ce code remplace le module par une maquette de module Jest ES qui contient toutes les propriétés d'origine du module à l'aide du paramètre d'usine de jest.mocks. La propriété __esModule est requise chaque fois que vous utilisez un paramètre d'usine dans jest.mock pour simuler un module ES (docs).

Si vous le souhaitez, vous pouvez également remplacer une fonction individuelle dans le paramètre d'usine. Par exemple, React Router générera une erreur si un consommateur appelle useNavigate() en dehors d'un contexte de routeur, nous pourrions donc utiliser jest.mock() pour remplacer cette fonction dans tout le fichier de test si nous le souhaitons :

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

Conclusion

J'espère que ces informations vous seront utiles lorsque vous rédigerez vos propres tests. Toutes les applications ne bénéficieront pas de la possibilité de revenir à l'implémentation par défaut lorsqu'aucune implémentation n'est fournie dans un test lui-même. En effet, de nombreuses applications voudront utiliser le même mock pour l’ensemble d’un fichier de test. Cependant, les techniques présentées dans cet article vous donneront un contrôle plus précis sur vos moqueries.

Faites-moi savoir si j'ai raté quelque chose ou s'il y a quelque chose que je n'ai pas inclus dans cet article et qui devrait être ici.

Déclaration de sortie Cet article est reproduit sur : https://dev.to/tylerlwsmith/override-functions-in-individual-tests-using-jest-dp5?1 En cas de violation, veuillez contacter [email protected] pour le supprimer.
Dernier tutoriel Plus>

Clause de non-responsabilité: Toutes les ressources fournies proviennent en partie d'Internet. En cas de violation de vos droits d'auteur ou d'autres droits et intérêts, veuillez expliquer les raisons détaillées et fournir une preuve du droit d'auteur ou des droits et intérêts, puis l'envoyer à l'adresse e-mail : [email protected]. Nous nous en occuperons pour vous dans les plus brefs délais.

Copyright© 2022 湘ICP备2022001581号-3