只模拟模块中的一个函数,但保留原来的功能

我只想从一个模块模拟一个函数(命名为 export) ,但保留其余模块函数的完整性。

使用 jest.mock('package-name')使所有导出的函数都成为模拟,这是我不想要的。

我尝试将命名的导出分布到模拟对象中..。

import * as utils from './utilities.js';


jest.mock(utils, () => ({
...utils
speak: jest.fn(),
}));


但有个错误:

jest.mock()的模块工厂不允许引用任何范围外的变量。

82216 次浏览

最直接的方法是使用 jest.spyOn,然后使用 .mockImplementation()。这将允许模块中的所有其他函数继续如何定义它们。

包裹:

import axios from 'axios';


jest.spyOn(axios, 'get');
axios.get.mockImplementation(() => { /* do thing */ });

对于具有指定导出的模块:

import * as utils from './utilities.js';


jest.spyOn(utils, 'speak');
utils.speak.mockImplementation(() => { /* do thing */ });

这里是医生: https://jestjs.io/docs/en/jest-object#jestspyonobject-methodname

对我来说,这个方法奏效了:

const utils = require('./utilities.js');
...
jest.spyOn(utils, 'speak').mockImplementation(() => jest.fn());

这个答案的亮点是 需求实际(),这是一个非常有用的实用程序,开玩笑说: “嘿,保持每个原始功能完好无损,并导入它们”。

jest.mock('./utilities.js', () => ({
...jest.requireActual('./utilities.js'),
speak: jest.fn(),
}));

让我们来看另一个常见的场景,您使用的是酵素 ShallowWrapper,但是它与 useContext () hook 不兼容,那么您要怎么做呢?虽然我相信有很多种方式,但这是我喜欢的一种:

import React from "react";


jest.mock("react", () => ({
...jest.requireActual("react"), // import and retain the original functionalities
useContext: jest.fn().mockReturnValue({foo: 'bar'}) // overwrite useContext
}))

这样做的好处是你仍然可以使用 如果使用 < strong > jest.spyOn (React,‘ useContext’) ,则不必担心将它们转换为 UseContext ()

手动模拟

您可以在与 utilities.js相同的级别上创建 __mocks__目录,然后在此目录中创建名为 utilities.js的文件。

utilities.js
const speak = () => "Function speak";
const add = (x, y) => x + y;
const sub = (x, y) => x - y;


module.exports = { speak, add, sub };

现在,保持所有内容不变,模仿 speak函数即可。

__mocks__/utilities.js
const speak = jest.fn(() => "Mocked function speak");
const add = (x, y) => x + y;
const sub = (x, y) => x - y;


module.exports = { speak, add, sub };

现在你可以嘲笑 utilities.js

utilities.test.js
const { speak, add, sub } = require("./utilities");


jest.mock("./utilities");


test("speak should be mocked", () => {
expect(speak()).toBe("Mocked function speak");
});

模拟节点模块

在与 node_modules相同的级别上创建一个名为 __mocks__的目录,并在该目录中添加一个文件‘ axios.js’。

__mocks__/axios.js
const axios = {
get: () => Promise.resolve({ data: { name: "Mocked name" } }),
};


module.exports = axios;
fetch.js
const axios = require("axios");


const fetch = async () => {
const { data } = await axios.get(
"https://jsonplaceholder.typicode.com/users/1"
);
return data.name;
};


module.exports = fetch;

对于节点模块,不需要显式调用 jest.mock("axios")

fetch.test.js
const fetch = require("./fetch");


test("axios should be mocked", async () => {
expect(await fetch()).toBe("Mocked name");
});

jest.mock内部的 jest.requireActual似乎是一种方法,但我需要添加一个代理,而不是对象传播,以防止类型错误 Cannot read properties of undefined (reading ...)可能发生在某些导入场景。

这是最终的结果:

jest.mock('the-module-to-mock', () => {
const actualModule = jest.requireActual('the-module-to-mock')


return new Proxy(actualModule, {
get: (target, property) => {
switch (property) {
// add cases for exports you want to mock
// 👇👇👇
case 'foo': {
return jest.fn() // add `mockImplementation` etc
}
case 'bar': {
return jest.fn()
}
// fallback to the original module
default: {
return target[property]
}
}
},
})
})

我采用了 Rico Kahler 的答案,并创建了这个通用函数:

function mockPartially(packageName: string, getMocks: (actualModule: any) => any) {
jest.doMock(packageName, () => {
const actualModule = jest.requireActual(packageName);
const mocks = getMocks(actualModule);


return new Proxy(actualModule, {
get: (target, property) => {
if (property in mocks) {
return mocks[property];
} else {
return target[property];
}
},
});
});
}


你可以像这样用它来模仿 loash:

mockPartially('lodash', (_actualLodash) => { //sometimes you need the actual module
return {
'isObject': () => true, //mock isObject
'isArray': () => true // mock isArray
}
});