Kadangkala anda ingin mengejek fungsi dalam sesetengah ujian tetapi bukan yang lain. Kadangkala anda ingin membekalkan olok-olok yang berbeza kepada ujian yang berbeza. Jest menjadikan perkara ini rumit: tingkah laku lalainya adalah untuk mengatasi fungsi pakej untuk keseluruhan fail ujian, bukan hanya satu ujian. Ini kelihatan pelik jika anda telah menggunakan alatan fleksibel seperti @patch Python atau bekas perkhidmatan Laravel.
Siaran ini akan menunjukkan kepada anda cara mengejek fungsi untuk ujian individu, kemudian berundur kepada pelaksanaan asal jika tiada olok-olok diberikan. Contoh akan diberikan untuk kedua-dua modul CommonJS dan ES. Teknik yang ditunjukkan dalam siaran ini akan berfungsi untuk kedua-dua modul pihak pertama dan pakej pihak ketiga.
Memandangkan kami akan merangkumi berbilang sistem modul dalam siaran ini, adalah penting untuk memahami apa itu.
CommonJS (disingkat CJS) ialah sistem modul dalam Node.js. Ia mengeksport fungsi menggunakan module.exports dan mengimport fungsi menggunakan require():
// CommonJS export function greet() { return "Hello, world!"; } module.exports = { greet };
// CommonJS import const getUsersList = require('./greet');
Modul ES (disingkat ESM) ialah sistem modul yang digunakan oleh penyemak imbas. Ia mengeksport fungsi menggunakan kata kunci eksport dan mengimport fungsi menggunakan kata kunci import:
// ES module export export default function greet() { return "Hello, world!"; }
// ES module import import { greet } from "./greet";
Kebanyakan pembangun JavaScript bahagian hadapan menggunakan modul ES semasa menulis siaran ini, dan banyak pembangun JS sebelah pelayan juga menggunakannya. Walau bagaimanapun, CommonJS masih menjadi lalai untuk Node. Tidak kira sistem yang anda gunakan, anda patut membaca keseluruhan artikel untuk mengetahui tentang sistem mengejek Jest.
Biasanya fail CommonJS akan mengeksport modul mereka menggunakan sintaks objek, seperti ditunjukkan di bawah:
// CommonJS export function greet() { return "Hello, world!"; } module.exports = { greet: greet };
Walau bagaimanapun, ia juga mungkin untuk mengeksport fungsi dengan sendirinya:
// CommonJS export function greet() { return "Hello, world!"; } module.exports = greet;
Saya tidak semestinya mengesyorkan melakukan ini dalam kod anda sendiri: mengeksport objek akan mengurangkan sakit kepala anda semasa membangunkan aplikasi anda. Walau bagaimanapun, adalah perkara biasa yang patut dibincangkan tentang cara mengejek fungsi yang dieksport kosong dalam CommonJS, kemudian berundur kepada asal jika ujian tidak menyediakan pelaksanaannya sendiri.
Katakanlah kami mempunyai fail CommonJS berikut yang ingin kami ejek semasa ujian:
// cjsFunction.js function testFunc() { return "original"; } module.exports = testFunc;
Kami boleh mengejeknya dalam ujian kami menggunakan kod berikut:
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); });
Apabila kami memanggil jest.mock("./cjsFunction"), ini menggantikan modul (fail dan semua eksportnya) dengan auto-olok-olok (dokumen). Apabila auto-olok-olok dipanggil, ia akan kembali tidak ditentukan. Walau bagaimanapun, ia akan menyediakan kaedah untuk mengatasi pelaksanaan olok-olok, nilai pulangan dan banyak lagi. Anda boleh melihat semua sifat dan kaedah yang disediakan dalam dokumentasi Jest Mock Functions.
Kita boleh menggunakan kaedah mockImplementation() untuk menetapkan pelaksanaan mock secara automatik kepada pelaksanaan modul asal. Jest menyediakan kaedah jest.requireActual() yang akan sentiasa memuatkan modul asal, walaupun ia sedang diejek.
Pelaksanaan olok-olok dan nilai pulangan dikosongkan secara automatik selepas setiap ujian, jadi kami boleh menghantar fungsi panggil balik ke fungsi Jest beforeEach() yang menetapkan pelaksanaan olok-olok kepada pelaksanaan asal sebelum setiap ujian. Kemudian mana-mana ujian yang ingin memberikan nilai pulangan atau pelaksanaannya sendiri boleh melakukannya secara manual dalam badan ujian.
Katakan kod di atas telah mengeksport objek dan bukannya satu fungsi:
// cjsModule.js function testFunc() { return "original"; } module.exports = { testFunc: testFunc, };
Ujian kami kemudiannya akan kelihatan seperti ini:
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); });
Kaedah jest.spyOn() membenarkan Jest merekodkan panggilan ke kaedah pada objek dan menyediakan penggantiannya sendiri. Ini hanya berfungsi pada objek dan kami boleh menggunakannya kerana modul kami mengeksport objek yang mengandungi fungsi kami.
Kaedah spyOn() adalah olok-olok, jadi keadaannya mesti ditetapkan semula. Dokumentasi Jest spyOn() mengesyorkan menetapkan semula keadaan menggunakan jest.restoreAllMocks() dalam panggilan balik afterEach(), iaitu perkara yang kami lakukan di atas. Jika kita tidak melakukan ini, olok-olok itu akan kembali tidak ditentukan dalam ujian seterusnya selepas spyOn() dipanggil.
Modul ES boleh mempunyai eksport lalai dan dinamakan:
// esmModule.js export default function () { return "original default"; } export function named() { return "original named"; }
Begini rupa ujian untuk fail di atas:
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"); });
Ini kelihatan hampir sama seperti contoh CommonJS sebelumnya, dengan beberapa perbezaan utama.
Pertama, kami mengimport modul kami sebagai import ruang nama.
import * as esmModule from "./esmModule";
Kemudian apabila kami ingin mengintip eksport lalai, kami menggunakan "lalai":
jest .spyOn(esmModule, "default") .mockImplementation(() => "mock implementation default");
Sometimes when trying to call jest.spyOn() with a third-party package, you'll get an error like the one below:
TypeError: Cannot redefine property: useNavigate at Function.defineProperty (<anonymous>)
When you run into this error, you'll need to mock the package that is causing the issue:
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(); });
This code replaces the module with a Jest ES Module mock that contains all of the module's original properties using jest.mocks's factory parameter. The __esModule property is required whenever using a factory parameter in jest.mock to mock an ES module (docs).
If you wanted, you could also replace an individual function in the factory parameter. For example, React Router will throw an error if a consumer calls useNavigate() outside of a Router context, so we could use jest.mock() to replace that function throughout the whole test file if we desired:
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; }; }, }; });
I hope this information is valuable as you write your own tests. Not every app will benefit from being able to fallback to the default implementation when no implementation is provided in a test itself. Indeed, many apps will want to use the same mock for a whole testing file. However, the techniques shown in this post will give you fine-grained control over your mocking.
Let me know if I missed something or if there's something that I didn't include in this post that should be here.
Atas ialah kandungan terperinci Gantikan fungsi dalam ujian individu menggunakan Jest. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!