为何我的Jest测试中URL.createObjectURL调用断言失败?
单元测试失败排查:第二个downloadPDFFile测试未触发createObjectURL调用
我写了下面两个单元测试:
test('should call downloadPDFFile with msSaveOrOpenBlob when the navigator is present', async () => { axios.get.mockResolvedValueOnce({ data: 'blob' }); window.navigator.msSaveOrOpenBlob = () => true; const msSaveOrOpenBlobSpy = jest.spyOn(window.navigator, 'msSaveOrOpenBlob'); await downloadPDFFile('www.pokebook/{+asset_path}', 'squirtle', '123', false); expect(msSaveOrOpenBlobSpy).toHaveBeenCalledWith('blob', 'squirtle.pdf'); }); test('should call downloadPDFFile with createObjectURL when the navigator is not present', async () => { axios.get.mockResolvedValueOnce({ data: 'blob' }); window.URL.createObjectURL = () => 'www.pokeball.com'; console.log(window.navigator); const urlCreateObjectSpy = jest.spyOn(window.URL, 'createObjectURL'); await downloadPDFFile('www.pokebook/{+asset_path}', 'charmander', '123', false); expect(urlCreateObjectSpy).toHaveBeenCalledWith('blob'); });
查阅资料后尝试设置window.URL.createObjectURL = () => {};,但第二个测试始终失败,报错如下:
expect(jest.fn()).toHaveBeenCalledWith(...expected) Expected: "blob" Number of calls: 0 557 | const urlCreateObjectSpy = jest.spyOn(window.URL, 'createObjectURL'); 558 | await downloadPDFFile('www.pokebook/{+asset_path}', 'charmander', '123', false); > 559 | expect(urlCreateObjectSpy).toHaveBeenCalledWith('blob'); | ^ 560 | }); 561 | }); 562 | });
被测试的函数代码:
const downloadPDFFile = (urlTemplate, name, readToken, isWatermarked) => { const config = { responseType: "blob" }; const pdfFileName = `${name.split(".")[0]}.pdf`; let formattedUrl; if (isWatermarked) { formattedUrl = urlTemplate.replace("{+temp}?", `?token=${readToken}&`); } else { formattedUrl = urlTemplate.replace("{+temp}", `?token=${readToken}`); } axios.get(formattedUrl, config).then((response) => { if (window.navigator && window.navigator.msSaveOrOpenBlob) { window.navigator.msSaveOrOpenBlob(response.data, pdfFileName); } else { const anchor = document.createElement("a"); anchor.download = pdfFileName; anchor.href = URL.createObjectURL(response.data); document.body.appendChild(anchor); anchor.click(); document.body.removeChild(anchor); } }); };
我怀疑是测试之间遗漏了清理步骤——第二个测试里console.log显示window.navigator对象还保留着之前的属性,这是不是导致测试失败的原因?
问题原因及解决方案
1. 测试间未清理全局变量
第一个测试给window.navigator添加了msSaveOrOpenBlob属性,Jest默认不会在测试间重置全局对象的自定义属性,所以第二个测试中window.navigator.msSaveOrOpenBlob依然存在,代码直接走了IE兼容分支,根本没进入调用createObjectURL的逻辑。
解决方法:
添加afterEach钩子,在每个测试结束后清理全局属性并重置mock:
afterEach(() => { // 清除添加的msSaveOrOpenBlob属性 if (window.navigator.msSaveOrOpenBlob) { delete window.navigator.msSaveOrOpenBlob; } // 重置所有Jest spy jest.restoreAllMocks(); });
2. downloadPDFFile未返回Promise,await无效
你的downloadPDFFile函数没有返回axios.get的Promise,测试里的await根本没等异步请求的回调执行完成,断言会提前触发,自然捕获不到createObjectURL的调用。
解决方法:
修改函数,返回axios请求的Promise:
const downloadPDFFile = (urlTemplate, name, readToken, isWatermarked) => { const config = { responseType: "blob" }; const pdfFileName = `${name.split(".")[0]}.pdf`; let formattedUrl; if (isWatermarked) { formattedUrl = urlTemplate.replace("{+temp}?", `?token=${readToken}&`); } else { formattedUrl = urlTemplate.replace("{+temp}", `?token=${readToken}`); } // 返回axios.get的Promise,让await能等待异步操作完成 return axios.get(formattedUrl, config).then((response) => { if (window.navigator && window.navigator.msSaveOrOpenBlob) { window.navigator.msSaveOrOpenBlob(response.data, pdfFileName); } else { const anchor = document.createElement("a"); anchor.download = pdfFileName; anchor.href = URL.createObjectURL(response.data); document.body.appendChild(anchor); anchor.click(); document.body.removeChild(anchor); } }); };
3. 第二个测试显式确保msSaveOrOpenBlob不存在
可以在第二个测试里主动清除该属性,避免残留影响:
test('should call downloadPDFFile with createObjectURL when the navigator is not present', async () => { axios.get.mockResolvedValueOnce({ data: 'blob' }); // 显式删除属性,确保进入非IE分支 delete window.navigator.msSaveOrOpenBlob; window.URL.createObjectURL = () => 'www.pokeball.com'; const urlCreateObjectSpy = jest.spyOn(window.URL, 'createObjectURL'); await downloadPDFFile('www.pokebook/{+asset_path}', 'charmander', '123', false); expect(urlCreateObjectSpy).toHaveBeenCalledWith('blob'); });
内容的提问来源于stack exchange,提问作者user18971245
相关产品推荐
相关产品推荐

