Javascript 如何使用Jest测试递归函数被调用X次?如果我使用间谍法,我的方法将永远挂起?
utils文件Javascript 如何使用Jest测试递归函数被调用X次?如果我使用间谍法,我的方法将永远挂起?,javascript,node.js,typescript,jestjs,Javascript,Node.js,Typescript,Jestjs,utils文件 const isStatusError=(err:any):err是StatusError=> 错误状态!==未定义; export const handleError=async(错误:any,emailer?:Mailer)=>{ const senderErrorEmail=async( 主题:字符串, 文本:字符串, 邮递员 ) => { 试一试{ 常量邮件:选取={ 发件人:config.email.user, 发送至:config.email.user, }; //2
const isStatusError=(err:any):err是StatusError=>
错误状态!==未定义;
export const handleError=async(错误:any,emailer?:Mailer)=>{
const senderErrorEmail=async(
主题:字符串,
文本:字符串,
邮递员
) => {
试一试{
常量邮件:选取={
发件人:config.email.user,
发送至:config.email.user,
};
//2.这会抛出一个错误
等待emailer?.send({…邮件,主题,文本});
}捕捉(错误){
//3.它应该递归调用此函数。。。
wait handleError(新邮件错误,emailer);
}
};
if(isStatusError(err)){
if(err instanceof scrape error){
console.log(“未能抓取网站:\n”,错误消息);
}
if(err instanceof AgendaJobError){
console.log(“作业”,错误消息);
//@TODO
}
if(err instanceof RepositoryError){
日志(“存储库:”);
控制台日志(错误消息);
//@TODO
}
//4.最终来到这里结束测试。。。
if(err instanceof EmailError){
console.log(“创建电子邮件服务失败”,错误);
}
//1.它先到这里。
if(err instanceof StatusError){
日志(“一般错误”,err);
等待sendErrorEmail(“错误”,“电子邮件发送人”);
}
}否则{
if(err instanceof Error){
日志(“一般错误”,错误消息);
}
日志(“一般错误”,err);
}
};
测试文件
import * as utils from "./app.utils";
import { Mailer } from "./services/email/Emailer.types";
import { StatusError } from "./shared/errors";
const getMockEmailer = (implementation?: Partial<Mailer>) =>
jest.fn<Mailer, []>(() => ({
service: "gmail",
port: 5432,
secure: false,
auth: {
user: "user",
pass: "pass",
},
verify: async () => true,
send: async () => true,
...implementation,
}))();
describe("error handling", () => {
it("should handle email failed to send", async () => {
const mockEmailer = getMockEmailer({
send: async () => {
throw new Error();
},
});
// This line is the problem. If I comment it out, it's all good.
const spiedHandleError = jest.spyOn(utils, "handleError");
// @TODO: Typescript will complain mockEmailer is missing a private JS Class variable (e.g. #transporter) if you remove `as any`.
await utils.handleError(new StatusError(500, ""), mockEmailer as any);
expect(spiedHandleError).toBeCalledTimes(2);
});
});
从“/app.utils”导入*作为utils;
从“/services/email/Emailer.types”导入{Mailer}”;
从“/shared/errors”导入{StatusError};
const getMockEmailer=(实现?:部分)=>
笑话fn(()=>({
服务:“gmail”,
港口:5432,
安全:错误,
认证:{
用户:“用户”,
通行证:“通行证”,
},
验证:async()=>true,
send:async()=>true,
实施
}))();
描述(“错误处理”,()=>{
它(“应该处理发送失败的电子邮件”,异步()=>{
const mockEmailer=getMockEmailer({
发送:异步()=>{
抛出新错误();
},
});
//这句话就是问题所在。如果我把它说出来,一切都好。
const-spiedHandleError=jest.spyOn(utils,“handleError”);
//@TODO:Typescript会抱怨mockEmailer缺少一个私有JS类变量(例如#transporter),如果您删除'as any'。
等待utils.handleError(新状态错误(500“”),mockEmailer,如有);
期望(spiedHandleError)。获得时间(2);
});
});
这个测试永远运行,这是因为我制作了一个间谍函数。
我尝试导入自己并运行wait-utils.handleError(new-EmailError(err),emailer)
,但它仍然挂起
因此发生的情况是:
utils
文件中,如果您对该const-spiedHandleError=jest.spyOn(utils,“handleError”)进行注释代码>退出,测试就可以了
有办法解决这个问题吗?不可能监视或模拟在定义的同一模块中使用的函数。这是JavaScript的限制,无法从另一个作用域访问变量。情况就是这样:
let moduleObj = (() => {
let foo = () => 'foo';
let bar = () => foo();
return { foo, bar };
})();
moduleObj.foo = () => 'fake foo';
moduleObj.foo() // 'fake foo'
moduleObj.bar() // 'foo'
编写函数的唯一方法是定义并一致地将其用作某些对象(如CommonJS导出)上的方法:
exports.handleError = async (...) => {
...
exports.handleError(...);
...
};
此解决方案不实用,且与ES模块不兼容。除非这样做,否则不可能监视递归调用的函数,如handleError
。有一个babel插件rewire
hack允许这样做,但已知它与Jest不兼容
正确的测试策略是不要断言函数调用了它自己(这样的断言可能对调试有用,但仅此而已),而是断言递归引起的效果。在这种情况下,这包括console.log
调用
spyOn
没有理由导致无限循环。由于没有提供模拟实现,它只是原始函数的包装器。如上所述,它无法影响内部handleError
调用,因此它不应该影响被测试函数的工作方式
监视utils
ES模块对象是不安全的,因为它是只读的,并且根据Jest设置可能导致错误。我意识到是我自己的逻辑导致了无限循环。我忘记将return语句添加到每个if语句中
我的间谍功能现在起作用了
const spiedHandleError = jest.spyOn(utils, "handleError");
await utils.handleError({
err: new StatusError(500, "error"),
emailer: mockEmailer,
});
expect(spiedHandleError).toBeCalledTimes(2);
expect(spiedHandleError.mock.calls).toEqual([
[{ err: new StatusError(500, "error"), emailer: mockEmailer }],
[
{
err: new EmailError("failed to send an error report email."),
emailer: mockEmailer,
},
],
]);
我意识到是我自己的逻辑导致了无限循环。我忘记将return语句添加到每个if语句中。
const spiedHandleError = jest.spyOn(utils, "handleError");
await utils.handleError({
err: new StatusError(500, "error"),
emailer: mockEmailer,
});
expect(spiedHandleError).toBeCalledTimes(2);
expect(spiedHandleError.mock.calls).toEqual([
[{ err: new StatusError(500, "error"), emailer: mockEmailer }],
[
{
err: new EmailError("failed to send an error report email."),
emailer: mockEmailer,
},
],
]);