
理解Jest中的异步错误测试
在javascript的异步编程中,promise是处理异步操作结果的核心机制。当异步操作失败时,promise会进入拒绝(rejected)状态,并通常伴随一个错误对象。jest提供了.rejects匹配器,专门用于测试promise是否被拒绝,并结合.tothrowerror()来验证拒绝的原因是否是预期的错误。
与同步函数的错误测试不同,同步函数使用expect(() => syncFun()).toThrowError(),需要将可能抛出错误的函数包裹在一个匿名函数中。然而,对于异步函数,.rejects的工作方式有所不同。
expect().rejects 的核心原理
expect().rejects旨在直接作用于一个Promise对象。当expect接收到一个Promise时,它会等待该Promise解析(resolve)或拒绝(reject)。如果Promise被拒绝,rejects链上的匹配器(如.toThrowError())将对拒绝值进行验证。
正确的异步错误测试方式
测试一个预期会抛出错误的异步函数asyncFun()的正确方式是直接将asyncFun()的调用结果(即一个Promise)传递给expect。
示例代码:
// 假设有一个异步函数,它会返回一个被拒绝的Promise
async function asyncFunThatThrows() {
return new Promise((resolve, reject) => {
// 模拟异步操作后抛出错误
setTimeout(() => {
reject(new Error('Async operation failed!'));
}, 100);
});
}
// 或者一个async函数直接抛出错误
async function anotherAsyncFunThatThrows() {
await new Promise(r => setTimeout(r, 50)); // 模拟一些异步工作
throw new Error('Another async error!');
}
describe('Testing async functions that throw errors', () => {
test('should correctly catch an error from a rejected promise', async () => {
const errorObj = new Error('Async operation failed!');
// 直接将Promise传递给expect
await expect(asyncFunThatThrows()).rejects.toThrowError(errorObj);
});
test('should correctly catch an error from an async function that throws', async () => {
const errorObj = new Error('Another async error!');
// 直接将Promise(async函数的返回值)传递给expect
await expect(anotherAsyncFunThatThrows()).rejects.toThrowError(errorObj);
});
});在这种模式下,asyncFunThatThrows()或anotherAsyncFunThatThrows()被调用后会立即返回一个Promise。await expect(...)会等待这个Promise完成,如果它被拒绝,rejects.toThrowError()就会对拒绝值进行匹配。
常见的误区与原因分析
一个常见的错误是将异步函数再次包裹在一个匿名异步函数中,然后将其传递给expect。
错误示例:
// 假设 asyncFun() 是一个会返回被拒绝Promise的异步函数
await expect(async () => {
await asyncFun();
}).rejects.toThrowError(errorObj);为什么这是错误的或不被推荐的:
- expect接收的是函数而非Promise: 在这个例子中,expect接收到的是一个匿名异步函数 async () => { await asyncFun() },而不是asyncFun()返回的Promise。尽管Jest在内部可能对传入的函数进行了某种处理,但这不是rejects设计的初衷和推荐用法。
- rejects期望Promise: .rejects匹配器是为Promise设计的。当它接收到一个函数时,其行为可能不符合预期,或者在未来的Jest版本中可能不再支持。Jest的官方文档明确指出.rejects应该用于Promise。
- 不必要的复杂性: 将一个异步函数包裹在另一个异步函数中,增加了不必要的嵌套和复杂性,同时也模糊了测试的意图。
这种写法在某些情况下可能“看似有效”,但它不是Jest官方文档推荐或保证的行为。正确的做法是始终将异步函数执行后返回的Promise直接传递给expect。
注意事项
- await的重要性: 在使用expect().rejects时,必须在expect调用前使用await。这是因为expect().rejects本身返回一个Promise,你需要等待这个Promise解析,以确保断言逻辑能够执行。
- Promise的拒绝状态: 只有当Promise进入拒绝状态时,.rejects才能捕获到错误。如果异步函数在执行过程中抛出异常,但没有被try...catch捕获并导致Promise拒绝,那么测试可能无法按预期工作。确保你的异步函数在失败时返回一个被拒绝的Promise。
总结
在Jest中测试异步函数抛出异常时,请牢记expect().rejects是为Promise设计的。正确的做法是直接将异步函数调用后返回的Promise传递给expect,例如 await expect(asyncFunctionCall()).rejects.toThrowError(error)。避免将异步函数包裹在另一个匿名函数中传递给expect,以确保测试的准确性、可读性以及与Jest最佳实践的一致性。遵循这一原则,你的异步错误测试将更加健壮和可靠。










