简体   繁体   English

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

[英]How to test a recursively called function in NodeJS?

I have a recurring function written in ES6/7 which is transpiled by babel. 我有一个用ES6 / 7编写的循环函数,由babel编译。 I have a recurring function created that checks if there is a user document, using mongoose. 我创建了一个定期函数,使用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
  }
}

If there is no user I am using the delay library to delay execution for a minute upon which the function is called recursively. 如果没有用户,我使用delay库来延迟执行一分钟,递归调用该函数。

This allows the halting of execution of the overall function until a user is found: 这允许暂停执行整个功能,直到找到用户:

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

The else branch is very easy to generate tests for. else分支很容易生成测试。 How do I create a test for the if branch that verifies the recursion is working properly? 如何为验证递归工作正常的if分支创建测试?

At the moment I have replaced the delay method with proxyquire during testing, subbing it for a custom delay function that just returns a value. 目前,我已经在测试期间用proxyquire替换了延迟方法,将其替换为仅返回值的自定义延迟函数。 At that point I can change the code to look like this: 那时我可以将代码更改为如下所示:

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

Issue there is that the source code is being changed to accommodate for the test. 问题是源代码正在被更改以适应测试。 Is there a better, cleaner solution? 有更好,更清洁的解决方案吗?

I'm not sure why you wanted to use a recursive solution instead of an iterative solution - but it might be easier to write it iteratively if for no other reason than so you don't blow the stack: 我不确定你为什么要使用递归解决方案而不是迭代解决方案 - 但如果没有其他原因而不是因为你没有吹掉堆栈,它可能更容易迭代地编写它:

  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);

Haven't tested or run this through an interpreter - but you get the idea. 没有通过翻译测试或运行 - 但你明白了。

Then in your testing mode - just provide a test user. 然后在您的测试模式下 - 只提供一个测试用户。 Since you'll probably need to write tests that use a reference to the user anyway. 因为您可能需要编写使用对用户的引用的测试。

There are several libraries that can be used for testing time-related events. 有几个库可用于测试与时间相关的事件。 As far as I know the most common solution is Lolex - https://github.com/sinonjs/lolex , earlier part of Sinon project. 据我所知,最常见的解决方案是Lolex - https://github.com/sinonjs/lolex,Sinon项目的早期部分。 The problem with Lolex is that it forwards the timers synchronously, thus ignoring events such as native node promises or process.nextTick (although it does fake setImmediate properly) - therefore you can get into some nasty issues. Lolex的问题在于它同步转发定时器,因此忽略了诸如本机节点promises或process.nextTick事件(尽管它确实伪造了setImmediate ) - 因此你可能会遇到一些令人讨厌的问题。 Be careful about external libraries - for example, bluebird caches the initial setImmediate , so you need to handle it somehow manually. 注意外部库 - 例如, bluebird缓存初始setImmediate ,因此您需要以某种方式手动处理它。

A different choice is Zurvan - https://github.com/Lewerow/zurvan (disclaimer: I wrote it). 另一个选择是Zurvan - https://github.com/Lewerow/zurvan (免责声明:我写的)。 It's a bit harder to tackle than Lolex, since it uses promises heavily, but behaves properly in presence of microqueued tasks ( process.nextTick , native Promise ) and has a built-in compatibility option for bluebird. 它比Lolex更难解决,因为它大量使用promises,但在存在微队任务( process.nextTick ,本机Promise )时表现正常,并且具有bluebird的内置兼容性选项。

Both libraries allow you to expire time-related events on arbirary length and override also Date instances (zurvan overrides process.uptime and process.hrtime as well). 这两个库允许您在arbirary长度上过期与时间相关的事件,并覆盖Date实例(zurvan也会覆盖process.uptimeprocess.hrtime )。 Neither of them is safe to use if you perform actual async IO in tests. 如果在测试中执行实际的异步IO,则它们都不安全。

I've written an example of how you could test your recursively called function here: 我写了一个例子,说明如何在这里测试递归调用的函数:

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

This test makes use of the Sinon javascript test library. 此测试使用Sinon javascript测试库。 You can set-up the behaviour of the stub on the nth call, hence you can simulate when no user is returned and then subsequently when a user is returned eg 您可以在第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({});

Hence onFirstCall simulates the first call and onSecondCall the recursive call. 因此onFirstCall模拟第一次调用,onSecondCall模拟递归调用。

Note that in the full example I've simplified checkIfUserExists, but the same test premise will apply for your full method. 请注意,在完整示例中,我简化了checkIfUserExists,但相同的测试前提将适用于您的完整方法。 Also note that you would additionally have to stub your delay method. 另请注意,您还需要存根延迟方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM