5月28日 04:23

Jest 如何测试异步代码?4 种方式与常见坑

Jest 测试异步代码有四种方式,按推荐优先级排列:async/await、resolves/rejects 匹配器、返回 Promise、done 回调。核心原则只有一个——让 Jest 知道测试什么时候算完

最常用的是 async/await,直接在 test 函数加 async,用 await 等待异步结果:

javascript
test('fetches user', async () => { const user = await getUser(1); expect(user.name).toBe('Alice'); });

如果你不需要对结果做复杂断言,.resolves / .rejects 更简洁:

javascript
test('resolves with data', () => { return expect(fetchData()).resolves.toBe('ok'); }); test('rejects on error', () => { return expect(fetchData()).rejects.toThrow('not found'); });

注意这里必须 return,否则 Jest 不会等 Promise 结束。

老项目里遇到回调风格的异步代码,用 done 参数:

javascript
test('callback style', done => { readFile('config.json', (err, data) => { if (err) { done(err); return; } try { expect(data.port).toBe(3000); done(); } catch (e) { done(e); } }); });

done 里面务必包 try-catch,否则 expect 失败会抛异常,done() 永远不被调用,你看到的不是断言错误而是超时错误,排查半天。

追问

忘记 return Promise 会怎样?

测试立即通过——而且是假通过。Jest 认为同步部分执行完就算结束,Promise 还没 resolve 就已经收工了。这是异步测试里最常见的坑,排查时看测试函数有没有 return 或 await 就行。

done 和 Promise 能混用吗?

不能。Jest 检测到同一个测试既传了 done 又返回了 Promise,会直接抛错,防止内存泄漏。选一种用到底。

async 函数抛错怎么测?

expect(fn()).toThrow() 对 async 无效,因为 async 函数返回的是 Promise 而不是直接抛错。正确写法:

javascript
await expect(getUser(-1)).rejects.toThrow('invalid id');

或者用 try-catch 配合 expect.assertions(1) 确保断言真的被执行了。

定时器相关的异步怎么测?

jest.useFakeTimers() 把定时器替换成模拟的,然后手动推进时间,不用真等:

javascript
jest.useFakeTimers(); test('debounce fires after delay', () => { const fn = jest.fn(); debounce(fn, 300); jest.advanceTimersByTime(300); expect(fn).toHaveBeenCalled(); });

实际项目里哪种用得最多?

async/await 占绝大多数场景,.resolves/.rejects 适合单行断言,done 基本只在对接老式回调 API 时才用。定时器模拟主要出现在防抖、轮询、超时重试这类逻辑里。

标签:Jest