javascript - 使用 Jest 在递归函数中模拟 Promise 和 Timers
问题描述
我试图在单元测试中覆盖一些代码(特别是 catch 块内的代码),我认为它失败是由于内存泄漏错误,可能是由于没有正确模拟计时器。
以下是主要代码的示例:
const delay = new Promise(resolve => setTimeout(() => resolve(), 1000);
const readCustomerStatus = async (retry = 2) => {
let content = '';
try{
await delay;
content = await fs.readFileSync(CUSTOMER_PATH,'utf8');
}
catch(error){
while(retry > 0){
readCustomerStatus(retry - 1);
}
}
return content;
}
这是单元测试代码:
test('Should return error when readFileSync fails'),async()=>{
fs.readFileSync = jest.fn()
.mockRejectedValueOnce(new Error('readFileSync failed'))
.mockRejectedValueOnce(new Error('readFileSync failed'));
const customerStatusResult = await readCustomerStatus();
expect(customerStatusResult).toThrow(‘readFileSync failed’);
}
当我从主代码中删除递归调用时,单元测试工作。看起来延迟承诺可能会以某种方式影响测试。我也尝试在 beforeAll 函数中添加 jest.useFakeTimers() ,但这似乎也没有帮助。
解决方案
看起来您的代码有多个问题,其中大多数与您(不)等待 Promises 的方式有关。
当您以Promise
这种方式创建时,它将立即开始执行,并且只会解决一次:
// Timer starts right away, which I believe it is not what you wanted
const delay = new Promise(resolve => setTimeout(() => resolve(), 1000);
所以这是行为:
await delay // Awaits `delay` Promise to be resolved.
// Depending on when it started, might be less than 1 second
await delay // This second invocation will be already resolved. 0 seconds delay.
您的递归版本也是如此。delay
您正在多次等待相同的Promise(每次递归),因此您实际上并没有在每次迭代中暂停 1 秒。
在这里,您还有另一个问题:
catch(error){
while(retry > 0){
readCustomerStatus(retry - 1); // issue: readCustomerStatus is async
}
}
在这一行中,您正在调用一个没有等待的异步函数,因此您只是创建了一个新的未处理的 Promise,并成功完成了第一个 Promise 的执行(因为您捕获了错误并忽略了它)。
您也不应该while
在重试中使用循环,因为这将导致一个永无止境的循环,因为retry
变量对于每个调用都是本地的。替换为if
将修复它。
最后,如果所有重试都失败,您将无法引发原始错误(将在下面显示如何修复)。
我不建议使用递归来处理重试,我认为它会使代码的可读性降低,并且还有一些不必要的堆栈分配。但是,出于教育目的,让我们修复此代码:
const delay = (ms=1000)=>new Promise(resolve=>setTimeout(resolve, ms));
const readCustomerStatus = async (retry = 2) => {
let content = '';
try {
// Notice we invoked function here to create a new Promise on each iteration
await delay();
content = await fs.readFileSync(CUSTOMER_PATH,'utf8');
} catch(error) {
if (retry > 0) {
// it is important to use await here and assign the result to `content`
content = await readCustomerStatus(retry - 1);
} else {
// This is our base case, so we just raise whatever error we found
throw error;
}
}
return content;
}
推荐阅读
- python - 如何同时查询多个配置元素?
- firebase - 将 Firestore 文档转换为颤振类
- java - 出现错误 -> 无效的标头字段名称,带有 32
- javascript - 限制await dns反向函数js的执行时间
- netlogo - 为什么带有步骤的 Netlogo `range` 命令在 1 以下表现不同
- python - 避免使用 pandas 进行 csv 编辑
- fragment - 在 GeoPandas 中合并和求和重叠多边形的值
- ios - 在 OSX/iOS 上使用 Clang 获取部分的地址
- python - numpy.savetxt - 保存具有不同类型的 np.array
- swift - 如何快速定义特定枚举案例的数组