Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/node.js/40.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Javascript 如何使用Jest测试递归函数被调用X次?如果我使用间谍法,我的方法将永远挂起?_Javascript_Node.js_Typescript_Jestjs - Fatal编程技术网

Javascript 如何使用Jest测试递归函数被调用X次?如果我使用间谍法,我的方法将永远挂起?

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

utils文件

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)
,但它仍然挂起

因此发生的情况是:

  • 它抛出了一个错误
  • 然后它会发现这是一个StatusError,这是一个自定义错误,它会输出错误并调用一个函数来发送电子邮件
  • 但是,尝试发送电子邮件会引发另一个错误
  • 然后它应该用EmailError调用自己
  • 它将检测到电子邮件错误,并仅输出错误
  • 从逻辑上讲,没有无限循环。 在
    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,
            },
          ],
        ]);