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

Javascript 如何在NodeJS中测试递归调用的函数?,javascript,node.js,testing,recursion,mocha.js,Javascript,Node.js,Testing,Recursion,Mocha.js,我有一个用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

我有一个用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替换了delay方法,将其替换为只返回值的自定义延迟函数。此时,我可以将代码更改为如下所示:

// 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 
  }
}

问题是源代码正在被修改以适应测试。有更好、更干净的解决方案吗

我不确定您为什么要使用递归解决方案而不是迭代解决方案,但如果没有其他原因,您可能更容易以迭代方式编写它,这样就不会破坏堆栈:

  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);
还没有通过解释器测试或运行它-但是你明白了


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

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

另一个选择是Zurvan(免责声明:我写的)。它比Lolex更难处理,因为它大量使用承诺,但在存在微队列任务(
process.nextTick
,native
Promise
)时行为正常,并且有一个内置的bluebird兼容性选项


这两个库都允许您根据任意长度终止与时间相关的事件,并覆盖
Date
实例(zurvan还覆盖
process.uptime
process.hrtime
)。如果在测试中执行实际的异步IO,则两者都不安全。

我在这里编写了一个示例,说明如何测试递归调用的函数:

此测试使用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,但相同的测试前提将适用于您的完整方法。另外请注意,您还需要对延迟方法进行存根。

谢谢您的回答。但是如果我在测试这个,我如何测试在没有用户存在的情况下触发的条件分支呢。如果我使用的是Mocha,测试最终将超时,因为它将继续调用自身。然后在运行测试之前显式取消用户设置,然后开始运行测试,稍等片刻,然后设置用户。谢谢Stephan,这就是我的想法。如果延迟时间过长(5分钟-1小时),该怎么办。有一种及时“向前跳”的Sinon helper方法,不是吗?五年过去了,没有一个答案被标记为被接受。有人帮过你吗?