测试异步代码
本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →
在 JavaScript 中,代码异步运行是很常见的。当测试异步代码时,Jest 需要知道被测试代码何时完成执行,然后才能继续下一个测试。Jest 提供了几种处理异步代码的方法。
Promises
在测试中返回一个 Promise,Jest 会等待该 Promise 被解析(resolve)。如果 Promise 被拒绝(reject),测试将失败。
例如,假设 fetchData 返回的 Promise 预期解析为字符串 'peanut butter'。我们可以这样测试:
test('the data is peanut butter', () => {
return fetchData().then(data => {
expect(data).toBe('peanut butter');
});
});
Async/Await
你也可以在测试中使用 async 和 await。要编写异步测试,请在传递给 test 的函数前添加 async 关键字。例如,相同的 fetchData 场景可以这样测试:
test('the data is peanut butter', async () => {
const data = await fetchData();
expect(data).toBe('peanut butter');
});
test('the fetch fails with an error', async () => {
expect.assertions(1);
try {
await fetchData();
} catch (error) {
expect(error).toMatch('error');
}
});
你可以将 async 和 await 与 .resolves 或 .rejects 结合使用。
test('the data is peanut butter', async () => {
await expect(fetchData()).resolves.toBe('peanut butter');
});
test('the fetch fails with an error', async () => {
await expect(fetchData()).rejects.toMatch('error');
});
这种情况下,async 和 await 本质上是对 Promise 示例相同逻辑的语法糖。
务必返回(或 await)Promise - 如果省略 return/await 语句,测试会在 fetchData 返回的 Promise 解析或拒绝前完成。
如果预期 Promise 会被拒绝,请使用 .catch 方法。确保添加 expect.assertions 来验证断言被调用的次数。否则,已实现的 Promise 不会导致测试失败。
test('the fetch fails with an error', () => {
expect.assertions(1);
return fetchData().catch(error => expect(error).toMatch('error'));
});
Callbacks
如果不使用 Promise,可以使用回调函数。例如,假设 fetchData 不返回 Promise,而是期望一个回调函数,即在获取数据完成后调用 callback(null, data)。你需要测试返回的数据是否为字符串 'peanut butter'。
默认情况下,Jest 测试在执行到末尾时即视为完成。这意味着以下测试_不会_按预期工作:
// Don't do this!
test('the data is peanut butter', () => {
function callback(error, data) {
if (error) {
throw error;
}
expect(data).toBe('peanut butter');
}
fetchData(callback);
});
问题在于测试会在 fetchData 完成后立即结束,而不会等待回调函数被调用。
test 提供了替代方案来解决此问题。不要将测试放在无参函数中,而是使用名为 done 的单参数。Jest 会等待 done 回调被调用后才结束测试。
test('the data is peanut butter', done => {
function callback(error, data) {
if (error) {
done(error);
return;
}
try {
expect(data).toBe('peanut butter');
done();
} catch (error) {
done(error);
}
}
fetchData(callback);
});
如果从未调用 done(),测试将失败(超时错误),这正是期望的结果。
如果 expect 断言失败,会抛出错误且 done() 不会被调用。如果想在测试日志中查看失败原因,需要将 expect 包裹在 try 块中,并在 catch 块中将错误传递给 done。否则,只会得到不透明的超时错误,而不会显示 expect(data) 收到的具体值。
如果同一个测试函数同时接收 done() 回调并返回 Promise,Jest 会抛出错误。这是为了避免测试中出现内存泄漏的预防措施。
.resolves / .rejects
你也可以在 expect 语句中使用 .resolves 匹配器,Jest 会等待该 Promise 解析。如果 Promise 被拒绝,测试会自动失败。
test('the data is peanut butter', () => {
return expect(fetchData()).resolves.toBe('peanut butter');
});
务必返回断言 - 如果省略 return 语句,测试会在 fetchData 返回的 Promise 解析前完成,导致 then() 中的回调没有机会执行。
如果预期 Promise 会被拒绝,请使用 .rejects 匹配器。其工作原理与 .resolves 类似。如果 Promise 被实现,测试会自动失败。
test('the fetch fails with an error', () => {
return expect(fetchData()).rejects.toMatch('error');
});
这些方式中没有哪一种特别优于其他方式,你可以在整个代码库中混合使用它们,甚至在单个文件中同时使用多种方式。具体选择完全取决于你觉得哪种风格能让测试代码更简洁明了。