如何在NodeJS中测试递归调用的函数

How to test a recursively called function in NodeJS?

本文关键字:递归 调用 函数 测试 NodeJS      更新时间:2023-09-26

我有一个用ES6/7编写的递归函数,它由babel转换。我创建了一个循环函数,使用mongoose检查是否有用户文档。

// Keep checking if there is a user, if there is let execution continue
export async function checkIfUserExists(){
  let user = await User.findOneAsync({});
  // if there is no user delay one minute and check again
  if(user === null){
    await delay(1000 * 60 * 1)
    return checkIfUserExists()
  } else {
    // otherwise, if there a user, let the execution move on
    return true
  }
}

如果没有用户,我将使用delay库将执行延迟一分钟,然后递归调用函数。

这允许停止执行整个功能,直到找到用户:

async function overallFunction(){
  await checkIfUserExists()
  // more logic
}

else分支很容易为其生成测试。如何为if分支创建一个测试来验证递归是否正常工作?

目前,我已经在测试过程中用proxyquire替换了延迟方法,将其替换为只返回值的自定义延迟函数。在这一点上,我可以更改代码如下:

// Keep checking if there is a user, if there is let execution continue
export async function checkIfUserExists(){
  let user = await User.findOneAsync({});
  // if there is no user delay one minute and check again
  if(user === null){
    let testing = await delay(1000 * 60 * 1)
    if (testing) return false
    return checkIfUserExists()
  } else {
    // otherwise, if there a user, let the execution move on
    return 
  }
}

问题是源代码正在更改以适应测试。有更好、更清洁的解决方案吗?

我在这里写了一个如何测试递归调用函数的例子:

https://jsfiddle.net/Fresh/qppprz20/

此测试使用Sinon javascript测试库。您可以在第n次调用时设置存根的行为,因此您可以模拟何时没有返回用户,然后在随后返回用户时,例如

// Stub the method behaviour using Sinon javascript framework
var user = new User();
var userStub = sinon.stub(user, 'findOneAsync');
userStub.onFirstCall().returns(null);
userStub.onSecondCall().returns({});

因此,onFirstCall模拟第一个调用,onSecondCall模拟递归调用。

请注意,在完整的示例中,我简化了checkIfUserExists,但相同的测试前提将适用于您的完整方法。还要注意的是,您还必须截断您的延迟方法。

有几个库可以用于测试与时间相关的事件。据我所知,最常见的解决方案是Lolex-https://github.com/sinonjs/lolex,Sinon项目的早期部分。Lolex的问题是,它同步转发计时器,从而忽略诸如本机节点承诺或process.nextTick之类的事件(尽管它确实正确地伪造了setImmediate),因此您可能会遇到一些棘手的问题。对于外部库要小心——例如,bluebird缓存初始的setImmediate,因此您需要以某种方式手动处理它。

另一种选择是Zurvanhttps://github.com/Lewerow/zurvan(免责声明:我写的)。它比Lolex更难处理,因为它大量使用promise,但在微队列任务(process.nextTick,原生Promise)存在时表现良好,并且有一个内置的蓝鸟兼容性选项。

这两个库都允许您在arbirary长度上过期与时间相关的事件,并覆盖Date实例(zurvan也覆盖process.uptimeprocess.hrtime)。如果在测试中执行实际的异步IO,那么这两种方法都不安全。

我不知道你为什么要使用递归解决方案而不是迭代解决方案,但如果没有其他原因,你不会破坏堆栈,迭代编写它可能会更容易:

  do{
  let user = await User.findOneAsync({});
  // if there is no user delay one minute and check again
  if(user === null){
    await delay(1000 * 60 * 1);
  }
  else{
    return true;
  }
  }while (!user);

还没有通过解释器测试或运行过这个,但你已经明白了。

然后在测试模式中,只需提供一个测试用户。因为您可能需要编写使用对用户的引用的测试。