You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 18:34:58