如何编写Jest测试验证async-mutex的Mutex.acquire方法会阻塞代码执行?
async-mutex 二次acquire阻塞逻辑的正确测试方案
原有写法的问题
- 直接
await第二次acquire时,由于第一个锁始终未释放,该异步调用会一直处于挂起状态,超过Jest默认5秒的异步回调超时阈值后,就会抛出你遇到的超时错误,根本无法执行后续的断言逻辑。 expect(await mutex.acquire()).toThrow("Timeout")的写法不符合异步断言规范:await会优先等待Promise执行完成,若Promise rejected会直接抛出错误,不会进入后续的expect判断。正确的异步抛错断言需要使用.rejects匹配器。- 你最初写法中的
toStillBeRunningAfterSpecifiedSeconds不是Jest原生支持的匹配器,无法直接用来判断异步函数是否仍在运行。
正确实现方案
方案1:状态标记+延迟检测(无依赖库原生能力,纯验证阻塞逻辑)
核心思路是记录第二次acquire的完成状态,等待一段合理时间后验证该状态未变更,证明调用被阻塞:
it('should block second acquire call when mutex is locked', async () => { const mutex = new Mutex(); const firstRelease = await mutex.acquire(); expect(mutex.isLocked()).toBeTruthy(); // 标记第二次acquire是否执行完成 let isSecondAcquireDone = false; // 不直接await,先保存第二次acquire的Promise实例 const secondAcquirePromise = mutex.acquire().then(() => { isSecondAcquireDone = true; }); // 等待1秒(时间可自行调整,需远小于Jest默认5秒超时) await new Promise(resolve => setTimeout(resolve, 1000)); // 状态仍为false,证明第二次acquire被阻塞未执行 expect(isSecondAcquireDone).toBe(false); // 测试结束后释放锁,清理pending Promise,避免影响其他用例 firstRelease(); await secondAcquirePromise; }, 10000); // 若调整了等待时间,可在此处单独设置当前用例的超时阈值,单位为毫秒
方案2:使用async-mutex自带超时参数(更简洁)
async-mutex的acquire方法支持传入第二个参数作为超时时间(单位毫秒),超过该时间未获取到锁会主动抛出超时错误,刚好可以用来验证阻塞逻辑:
it('should throw timeout error when second acquire exceeds wait time', async () => { const mutex = new Mutex(); const release = await mutex.acquire(); expect(mutex.isLocked()).toBeTruthy(); // 第二次acquire设置1秒超时,拿不到锁会直接reject await expect(mutex.acquire(1000)).rejects.toThrow('Mutex timeout'); // 释放锁清理资源 release(); });
注意:不同版本的async-mutex超时错误提示可能存在差异,可根据实际运行的错误信息调整toThrow的匹配内容。
注意事项
- 方案1的等待时间建议控制在3秒以内,避免触碰到Jest默认超时阈值,确有需要可在it的第三个参数单独指定当前用例的超时时间。
- 无论用哪种方案,测试结束后都要主动释放持有的锁,避免残留的pending Promise影响其他测试用例的运行。
内容的提问来源于stack exchange,提问作者Archimedes Trajano
相关产品推荐
相关产品推荐

