Às vezes você deseja simular uma função em alguns testes, mas não em outros. Às vezes você deseja fornecer simulações diferentes para testes diferentes. Jest torna isso complicado: seu comportamento padrão é substituir a função de um pacote para um arquivo de teste inteiro, não apenas para um único teste. Isso parece estranho se você usou ferramentas flexíveis como o @patch do Python ou o contêiner de serviço do Laravel.
Esta postagem mostrará como simular funções para testes individuais e, em seguida, retornar à implementação original se nenhuma simulação for fornecida. Exemplos serão dados para os módulos CommonJS e ES. As técnicas demonstradas nesta postagem funcionarão tanto para módulos originais quanto para pacotes de terceiros.
Como abordaremos vários sistemas de módulos nesta postagem, é importante entender o que eles são.
CommonJS (abreviado como CJS) é o sistema de módulos em Node.js. Ele exporta funções usando module.exports e importa funções usando require():
// CommonJS export function greet() { return "Hello, world!"; } module.exports = { greet };
// CommonJS import const getUsersList = require('./greet');
Módulos ES (abreviado como ESM) é o sistema de módulos usado pelo navegador. Ele exporta funções usando a palavra-chave export e importa funções usando a palavra-chave import:
// ES module export export default function greet() { return "Hello, world!"; }
// ES module import import { greet } from "./greet";
A maioria dos desenvolvedores front-end de JavaScript usam módulos ES no momento em que escrevemos esta postagem, e muitos desenvolvedores JS do lado do servidor também os usam. No entanto, CommonJS ainda é o padrão para Node. Independentemente do sistema que você usa, vale a pena ler o artigo inteiro para aprender sobre o sistema de zombaria do Jest.
Normalmente, um arquivo CommonJS exportará seus módulos usando sintaxe de objeto, como mostrado abaixo:
// CommonJS export function greet() { return "Hello, world!"; } module.exports = { greet: greet };
No entanto, também é possível exportar uma função por si só:
// CommonJS export function greet() { return "Hello, world!"; } module.exports = greet;
Eu não recomendaria necessariamente fazer isso em seu próprio código: exportar um objeto lhe dará menos dores de cabeça durante o desenvolvimento de seu aplicativo. No entanto, é comum o suficiente para que valha a pena discutir como zombar de uma função exportada simples no CommonJS e, em seguida, retornar ao original se um teste não fornecer sua própria implementação.
Digamos que temos o seguinte arquivo CommonJS que gostaríamos de simular durante os testes:
// cjsFunction.js function testFunc() { return "original"; } module.exports = testFunc;
Poderíamos simular isso em nossos testes usando o seguinte 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); });
Quando chamamos jest.mock("./cjsFunction"), isso substitui o módulo (o arquivo e todas as suas exportações) por uma simulação automática (docs). Quando uma simulação automática é chamada, ela retornará indefinido. No entanto, ele fornecerá métodos para substituir a implementação do mock, o valor de retorno e muito mais. Você pode ver todas as propriedades e métodos que ele fornece na documentação do Jest Mock Functions.
Podemos usar o método mockImplementation() do mock para definir automaticamente a implementação do mock para a implementação do módulo original. Jest fornece um método jest.requireActual() que sempre carregará o módulo original, mesmo se ele estiver sendo ridicularizado.
Implementações simuladas e valores de retorno são limpos automaticamente após cada teste, para que possamos passar uma função de retorno de chamada para a função beforeEach() de Jest que define a implementação da simulação para a implementação original antes de cada teste. Então, qualquer teste que desejar fornecer seu próprio valor de retorno ou implementação poderá fazer isso manualmente dentro do corpo de teste.
Digamos que o código acima tenha exportado um objeto em vez de uma única função:
// cjsModule.js function testFunc() { return "original"; } module.exports = { testFunc: testFunc, };
Nossos testes ficariam assim:
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); });
O método jest.spyOn() permite que Jest grave chamadas para um método em um objeto e forneça seu próprio substituto. Isso apenas funciona em objetos, e podemos usá-lo porque nosso módulo está exportando um objeto que contém nossa função.
O método spyOn() é uma simulação, portanto seu estado deve ser redefinido. A documentação do Jest spyOn() recomenda redefinir o estado usando jest.restoreAllMocks() em um retorno de chamada afterEach(), que foi o que fizemos acima. Se não fizéssemos isso, o mock retornaria indefinido no próximo teste após spyOn() ser chamado.
Os módulos ES podem ter exportações padrão e nomeadas:
// esmModule.js export default function () { return "original default"; } export function named() { return "original named"; }
Aqui estão os testes para o arquivo acima:
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"); });
Isso parece quase igual ao exemplo anterior do CommonJS, com algumas diferenças importantes.
Primeiro, estamos importando nosso módulo como uma importação de namespace.
import * as esmModule from "./esmModule";
Então, quando quisermos espionar a exportação padrão, usamos "default":
jest .spyOn(esmModule, "default") .mockImplementation(() => "mock implementation default");
Às vezes, ao tentar chamar jest.spyOn() com um pacote de terceiros, você receberá um erro como este abaixo:
TypeError: Cannot redefine property: useNavigate at Function.defineProperty ()
Ao encontrar esse erro, você precisará simular o pacote que está causando o 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 substitui o módulo por uma simulação do módulo Jest ES que contém todas as propriedades originais do módulo usando o parâmetro de fábrica de jest.mocks. A propriedade __esModule é necessária sempre que usar um parâmetro de fábrica em jest.mock para simular um módulo ES (docs).
Se desejar, você também pode substituir uma função individual no parâmetro de fábrica. Por exemplo, React Router gerará um erro se um consumidor chamar useNavigate() fora de um contexto de roteador, então poderíamos usar jest.mock() para substituir essa função em todo o arquivo de teste, se desejarmos:
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 informação seja valiosa à medida que você escreve seus próprios testes. Nem todo aplicativo se beneficiará com a possibilidade de retornar à implementação padrão quando nenhuma implementação for fornecida em um teste em si. Na verdade, muitos aplicativos vão querer usar a mesma simulação para um arquivo de teste inteiro. No entanto, as técnicas mostradas nesta postagem lhe darão um controle refinado sobre sua zombaria.
Deixe-me saber se perdi alguma coisa ou se há algo que não incluí neste post e que deveria estar aqui.
Isenção de responsabilidade: Todos os recursos fornecidos são parcialmente provenientes da Internet. Se houver qualquer violação de seus direitos autorais ou outros direitos e interesses, explique os motivos detalhados e forneça prova de direitos autorais ou direitos e interesses e envie-a para o e-mail: [email protected]. Nós cuidaremos disso para você o mais rápido possível.
Copyright© 2022 湘ICP备2022001581号-3