A veces quieres simular una función en algunas pruebas pero no en otras. A veces es necesario proporcionar diferentes simulacros para diferentes pruebas. Jest hace que esto sea complicado: su comportamiento predeterminado es anular la función de un paquete para un archivo de prueba completo, no solo para una prueba única. Esto parece extraño si ha utilizado herramientas flexibles como @patch de Python o el contenedor de servicios de Laravel.
Esta publicación le mostrará cómo simular funciones para pruebas individuales y luego recurrir a la implementación original si no se proporcionó ninguna simulación. Se darán ejemplos para los módulos CommonJS y ES. Las técnicas demostradas en esta publicación funcionarán tanto para módulos propios como para paquetes de terceros.
Dado que cubriremos varios sistemas de módulos en esta publicación, es importante comprender cuáles son.
CommonJS (abreviado CJS) es el sistema de módulos en Node.js. Exporta funciones usando module.exports e importa funciones usando require():
// CommonJS export function greet() { return "Hello, world!"; } module.exports = { greet };
// CommonJS import const getUsersList = require('./greet');
Módulos ES (abreviado ESM) es el sistema de módulos que utiliza el navegador. Exporta funciones usando la palabra clave export e importa funciones usando la palabra clave import:
// ES module export export default function greet() { return "Hello, world!"; }
// ES module import import { greet } from "./greet";
La mayoría de los desarrolladores de JavaScript frontend usan módulos ES al momento de escribir esta publicación, y muchos desarrolladores de JS del lado del servidor también los usan. Sin embargo, CommonJS sigue siendo el valor predeterminado para Node. Independientemente del sistema que utilices, vale la pena leer el artículo completo para conocer el sistema de burla de Jest.
Normalmente, un archivo CommonJS exportará sus módulos utilizando la sintaxis de objeto, como se muestra a continuación:
// CommonJS export function greet() { return "Hello, world!"; } module.exports = { greet: greet };
Sin embargo, también es posible exportar una función por sí sola:
// CommonJS export function greet() { return "Hello, world!"; } module.exports = greet;
No necesariamente recomendaría hacer esto en tu propio código: exportar un objeto te dará menos dolores de cabeza mientras desarrollas tu aplicación. Sin embargo, es bastante común que valga la pena discutir cómo simular una función exportada simple en CommonJS y luego recurrir al original si una prueba no proporciona su propia implementación.
Digamos que tenemos el siguiente archivo CommonJS del que nos gustaría simular durante las pruebas:
// cjsFunction.js function testFunc() { return "original"; } module.exports = testFunc;
Podríamos burlarnos de ello en nuestras pruebas usando el siguiente código:
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); });
Cuando llamamos a jest.mock("./cjsFunction"), esto reemplaza el módulo (el archivo y todas sus exportaciones) con una simulación automática (docs). Cuando se llama a una simulación automática, devolverá un valor indefinido. Sin embargo, proporcionará métodos para anular la implementación del simulacro, el valor de retorno y más. Puede ver todas las propiedades y métodos que proporciona en la documentación de Jest Mock Functions.
Podemos usar el método mockImplementation() del simulacro para configurar automáticamente la implementación del simulacro a la implementación del módulo original. Jest proporciona un método jest.requireActual() que siempre cargará el módulo original, incluso si se está burlando de él actualmente.
Las implementaciones simuladas y los valores de retorno se borran automáticamente después de cada prueba, por lo que podemos pasar una función de devolución de llamada a la función beforeEach() de Jest que establece la implementación del simulacro en la implementación original antes de cada prueba. Luego, cualquier prueba que desee proporcionar su propio valor de retorno o implementación puede hacerlo manualmente dentro del cuerpo de la prueba.
Digamos que el código anterior había exportado un objeto en lugar de una sola función:
// cjsModule.js function testFunc() { return "original"; } module.exports = { testFunc: testFunc, };
Nuestras pruebas entonces se verían así:
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); });
El método jest.spyOn() permite a Jest registrar llamadas a un método en un objeto y proporcionar su propio reemplazo. Esto solo funciona en objetos y podemos usarlo porque nuestro módulo está exportando un objeto que contiene nuestra función.
El método spyOn() es simulado, por lo que se debe restablecer su estado. La documentación de Jest spyOn() recomienda restablecer el estado usando jest.restoreAllMocks() en una devolución de llamada afterEach(), que es lo que hicimos anteriormente. Si no hiciéramos esto, el simulacro volvería indefinido en la siguiente prueba después de que se llamara a spyOn().
Los módulos ES pueden tener exportaciones predeterminadas y con nombre:
// esmModule.js export default function () { return "original default"; } export function named() { return "original named"; }
Así es como se verían las pruebas para el archivo anterior:
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"); });
Esto se ve casi igual que el ejemplo anterior de CommonJS, con un par de diferencias clave.
Primero, importaremos nuestro módulo como una importación de espacio de nombres.
import * as esmModule from "./esmModule";
Luego, cuando queremos espiar la exportación predeterminada, usamos "default":
jest .spyOn(esmModule, "default") .mockImplementation(() => "mock implementation default");
A veces, al intentar llamar a jest.spyOn() con un paquete de terceros, obtendrás un error como el siguiente:
TypeError: Cannot redefine property: useNavigate at Function.defineProperty ()
Cuando te encuentres con este error, tendrás que burlarte del paquete que está causando el problema:
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(); });
Este código reemplaza el módulo con una simulación del módulo Jest ES que contiene todas las propiedades originales del módulo usando el parámetro de fábrica de jest.mocks. La propiedad __esModule es necesaria siempre que se utiliza un parámetro de fábrica en jest.mock para simular un módulo ES (docs).
Si lo desea, también puede reemplazar una función individual en el parámetro de fábrica. Por ejemplo, React Router arrojará un error si un consumidor llama a useNavigate() fuera del contexto de un enrutador, por lo que podríamos usar jest.mock() para reemplazar esa función en todo el archivo de prueba si así lo deseamos:
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; }; }, }; });
Espero que esta información sea valiosa mientras escribes tus propias pruebas. No todas las aplicaciones se beneficiarán de poder recurrir a la implementación predeterminada cuando no se proporciona ninguna implementación en una prueba en sí. De hecho, muchas aplicaciones querrán utilizar el mismo modelo para un archivo de prueba completo. Sin embargo, las técnicas que se muestran en esta publicación te brindarán un control detallado sobre tus burlas.
Avísame si me perdí algo o si hay algo que no incluí en esta publicación y que debería estar aquí.
Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.
Copyright© 2022 湘ICP备2022001581号-3