javascript jestjs

javascript - ¿Cómo nos burlamos de las dependencias con Jest_per test_?



jestjs (2)

Respuesta corta

El uso require agarrar un módulo nuevo en cada función de prueba después de configurar simulacros.

it("should print breakfast (mocked)", () => { jest.doMock(...); const getMeal = require("../src/meal").default; ... });

o

Convierta Food en una función y ponga una llamada a jest.mock en el alcance del módulo.

import getMeal from "../src/meal"; import food from "../src/food"; jest.mock("../src/food"); food.mockReturnValue({ ... }); ...

Respuesta larga

Hay un fragmento en el manual Jest que dice:

Nota: para simularse correctamente, Jest necesita que jest.mock (''moduleName'') esté en el mismo ámbito que la declaración require / import.

El mismo manual también establece:

Si está utilizando las importaciones de módulos ES, normalmente estará inclinado a poner sus declaraciones de importación en la parte superior del archivo de prueba. Pero a menudo es necesario que Jest use un simulacro antes de que los módulos lo usen. Por este motivo, Jest elevará automáticamente las llamadas jest.mock a la parte superior del módulo (antes de realizar cualquier importación).

Las importaciones de ES6 se resuelven en el alcance del módulo antes de que se ejecute cualquiera de las funciones de prueba. Por lo tanto, para que se apliquen los simulacros, deben declararse fuera de las funciones de prueba y antes de importar cualquier módulo. El plugin de Babel de Jest " jest.mock " jest.mock declaraciones jest.mock al principio del archivo para que se ejecuten antes de que se realicen las importaciones. Tenga en cuenta que jest.doMock se jest.doMock deliberadamente .

Uno puede estudiar el código generado echando un vistazo al directorio de caché de Jest (ejecute jest --showConfig para conocer la ubicación).

El módulo de food en el ejemplo es difícil de burlar porque es un objeto literal y no una función. La forma más fácil es forzar una recarga del módulo cada vez que se necesita cambiar el valor.

Opción 1a: no usar módulos ES6 de las pruebas

Las declaraciones de importación de ES6 deben tener un ámbito de módulo, sin embargo, el require "bueno" no tiene tal limitación y puede llamarse desde el alcance de un método de prueba.

describe("meal tests", () => { beforeEach(() => { jest.resetModules(); }); it("should print dinner", () => { const getMeal = require("../src/meal").default; expect(getMeal()).toBe( "Good evening. Dinner is green beans and rice. Yum!" ); }); it("should print breakfast (mocked)", () => { jest.doMock("../src/food", () => ({ type: "breakfast", veg: "avocado", carbs: "toast" })); const getMeal = require("../src/meal").default; // ...this works now expect(getMeal()).toBe("Good morning. Breakfast is avocado and toast. Yum!"); }); });

Opción 1b: recargar el módulo en cada invocación

También se puede envolver la función bajo prueba.

En lugar de

import getMeal from "../src/meal";

utilizar

const getMeal = () => require("../src/meal").default();

Opción 2: Registra el simulacro y llama a funciones reales de forma predeterminada

Si el módulo de food expone una función y no un literal, podría ser burlado. La instancia simulada es mutable y se puede cambiar de prueba a prueba.

src / food.js

const Food = { carbs: "rice", veg: "green beans", type: "dinner" }; export default function() { return Food; }

src / meal.js

import getFood from "./food"; function formatMeal() { const { carbs, veg, type } = getFood(); if (type === "dinner") { return `Good evening. Dinner is ${veg} and ${carbs}. Yum!`; } else if (type === "breakfast") { return `Good morning. Breakfast is ${veg} and ${carbs}. Yum!`; } else { return "No soup for you!"; } } export default function getMeal() { const meal = formatMeal(); return meal; }

__tests __ / meal_test.js

import getMeal from "../src/meal"; import food from "../src/food"; jest.mock("../src/food"); const realFood = jest.requireActual("../src/food").default; food.mockImplementation(realFood); describe("meal tests", () => { beforeEach(() => { jest.resetModules(); }); it("should print dinner", () => { expect(getMeal()).toBe( "Good evening. Dinner is green beans and rice. Yum!" ); }); it("should print breakfast (mocked)", () => { food.mockReturnValueOnce({ type: "breakfast", veg: "avocado", carbs: "toast" }); // ...this works now expect(getMeal()).toBe("Good morning. Breakfast is avocado and toast. Yum!"); }); });

Por supuesto, hay otras opciones como dividir la prueba en dos módulos donde un archivo configura un simulacro y el otro usa un módulo real o devuelve un objeto mutable en lugar de una exportación predeterminada para el módulo de food para que pueda ser modificado por cada prueba y luego reinicie manualmente en beforeEach .

(Aquí está el repro mínimo completo: https://github.com/magicmark/jest_question )

Dada la siguiente aplicación:

src / food.js

const Food = { carbs: "rice", veg: "green beans", type: "dinner" }; export default Food;

src / food.js

import Food from "./food"; function formatMeal() { const { carbs, veg, type } = Food; if (type === "dinner") { return `Good evening. Dinner is ${veg} and ${carbs}. Yum!`; } else if (type === "breakfast") { return `Good morning. Breakfast is ${veg} and ${carbs}. Yum!`; } else { return "No soup for you!"; } } export default function getMeal() { const meal = formatMeal(); return meal; }

Tengo la siguiente prueba:

__tests __ / meal_test.js

import getMeal from "../src/meal"; describe("meal tests", () => { beforeEach(() => { jest.resetModules(); }); it("should print dinner", () => { expect(getMeal()).toBe( "Good evening. Dinner is green beans and rice. Yum!" ); }); it("should print breakfast (mocked)", () => { jest.doMock("../src/food", () => ({ type: "breakfast", veg: "avocado", carbs: "toast" })); // prints out the newly mocked food! console.log(require("../src/food")); // ...but we didn''t mock it in time, so this fails! expect(getMeal()).toBe("Good morning. Breakfast is avocado and toast. Yum!"); }); });

¿Cómo me burlo de Food correctamente por prueba ? En otras palabras, solo quiero aplicar el simulacro para el caso de prueba "should print breakfast (mocked)" simulado "should print breakfast (mocked)" .

También me gustaría no cambiar el código fuente de la aplicación de manera ideal (aunque tal vez tener Food sea ​​una función que devuelva un objeto sería aceptable, aún así no puedo hacer que eso funcione).

Cosas que ya he probado:

  • enhebre el objeto Food a través de getMeal + use la inyección de dependencia en formatMeal
    • (El punto central de este enfoque IRL es que no queremos unir a Food en toda la aplicación)
  • manual mock + jest.mock() : es posible que la respuesta esté aquí, pero es difícil controlar el valor aquí y restablecerlo por prueba debido a la rareza del tiempo de importación
    • El uso de jest.mock() en la parte superior lo anularía en cada caso de prueba, y no puedo averiguar cómo cambiar o restablecer el valor de Food por prueba.

¡Gracias!


La respuesta de @anttix es la mejor, pero aquí hay otro ángulo que podría ser útil en otros escenarios.

babel-plugin-rewire permite import Food from "./food"; ser superado por la prueba.

En primer lugar, yarn add babel-plugin-rewire

babel.config.js

const presets = [ [ "@babel/env", { targets: { node: ''current'', }, }, ], ]; const plugins = [ "babel-plugin-rewire" ]; module.exports = { presets, plugins };

meal_test.js

import getMeal from "../src/meal"; import Food from "../src/food"; import { __RewireAPI__ as RewireAPI } from "../src/meal"; describe("meal tests", () => { // beforeEach(() => { // jest.resetModules(); // }); afterEach(() => { RewireAPI.__Rewire__(''Food'', Food) }); it("should print dinner", () => { expect(getMeal()).toBe( "Good evening. Dinner is green beans and rice. Yum!" ); }); it("should print breakfast (mocked)", () => { const mockFood = { type: "breakfast", veg: "avocado", carbs: "toast" }; RewireAPI.__Rewire__(''Food'', mockFood) expect(getMeal()).toBe("Good morning. Breakfast is avocado and toast. Yum!"); }); it("should print dinner #2", () => { expect(getMeal()).toBe( "Good evening. Dinner is green beans and rice. Yum!" ); }); });