Jest测试download方法报错:接收值必须是mock或spy函数
解决FileSaver.saveAs单元测试的mock失效问题
问题场景
要测试的download函数:
export const download = (file, filename) => { FileSaver.saveAs(file, filename); };
编写的测试代码:
import { download } from "../../../utils/methods/utils"; import FileSaver from "file-saver"; import { item } from "../../__mocks__/responses/export" describe("Test download", () => { it("Can handle download", () => { jest.mock("file-saver", () => ({ saveAs: jest.fn() })); download(item.file, "test.xls"); expect(FileSaver.saveAs).toHaveBeenCalledTimes(1); }); });
运行测试时出现错误:
expect(received).toHaveBeenCalledTimes(expected) Matcher error: received value must be a mock or spy function Received has type: function Received has value: [Function anonymous] 9 | })); 10 | download(item.file, "test.xls"); > 11 | expect(FileSaver.saveAs).toHaveBeenCalledTimes(1); | ^ 12 | }); 13 | }); 14 | at Object.<anonymous> (src/__tests__/utils/methods/download.test.js:11:30)
错误原因
jest.mock()的调用位置错误——它被放在了it测试用例内部。Jest的模块mock是在模块加载阶段生效的,当测试用例执行时,FileSaver模块已经被导入并加载完成,此时再调用jest.mock()已经无法替换原始模块,导致你拿到的FileSaver.saveAs还是真实的函数,不是Jest的mock函数,因此触发了“必须是mock或spy函数”的报错。
解决方法
方案一:将jest.mock移到模块作用域
把jest.mock()移到测试文件的最外层(所有导入语句之后或之前都可以,Jest会自动将jest.mock提升到模块顶部):
import { download } from "../../../utils/methods/utils"; import FileSaver from "file-saver"; import { item } from "../../__mocks__/responses/export" // 全局mock file-saver模块 jest.mock("file-saver", () => ({ saveAs: jest.fn() })); describe("Test download", () => { it("Can handle download", () => { // 重置mock状态,避免不同用例之间的干扰 FileSaver.saveAs.mockClear(); download(item.file, "test.xls"); expect(FileSaver.saveAs).toHaveBeenCalledTimes(1); // 可选:验证调用参数是否正确 expect(FileSaver.saveAs).toHaveBeenCalledWith(item.file, "test.xls"); }); });
方案二:使用jest.spyOn局部mock方法
如果只想在当前测试用例中mocksaveAs方法,不影响其他用例,可以用jest.spyOn来替换目标方法:
import { download } from "../../../utils/methods/utils"; import FileSaver from "file-saver"; import { item } from "../../__mocks__/responses/export" describe("Test download", () => { it("Can handle download", () => { // 创建spy,替换saveAs为mock函数 const saveAsSpy = jest.spyOn(FileSaver, "saveAs").mockImplementation(() => {}); download(item.file, "test.xls"); expect(saveAsSpy).toHaveBeenCalledTimes(1); expect(saveAsSpy).toHaveBeenCalledWith(item.file, "test.xls"); // 测试结束后恢复原始方法,避免影响其他测试 saveAsSpy.mockRestore(); }); });
方案对比
- 方案一适合需要全局mock整个
file-saver模块的场景,所有测试用例都会复用这个mock。 - 方案二更灵活,仅在当前用例中生效,测试后自动恢复原始实现,适合局部mock的场景。
内容的提问来源于stack exchange,提问作者sineverba
相关产品推荐
相关产品推荐

