Skip to main content
Version: 29.7

测试异步代码


在 JavaScript 中,代码异步运行很常见。 当你的代码异步运行时,Jest 需要知道它正在测试的代码何时完成,然后才能继续进行另一个测试。 Jest 有几种方法来处理这个问题。

Promise

从测试返回一个 promise,Jest 将等待该 promise 解决。 如果 promise 被拒绝,测试就会失败。

例如,假设 fetchData 返回一个应解析为字符串 'peanut butter' 的 Promise。 我们可以用以下方法测试它:

test('the data is peanut butter', () => {
return fetchData().then(data => {
expect(data).toBe('peanut butter');
});
});

异步/等待

或者,你可以在测试中使用 asyncawait。 要编写异步测试,请在传递给 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 (e) {
expect(e).toMatch('error');
}
});

你可以将 asyncawait.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');
});

在这些情况下,asyncawait 是有效的语法糖,其逻辑与 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(e => expect(e).toMatch('error'));
});

回调

如果不使用 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

你还可以在你的期望语句中使用 .resolves 匹配器,Jest 将等待该 promise 解决。 如果 promise 被拒绝,测试将自动失败。

test('the data is peanut butter', () => {
return expect(fetchData()).resolves.toBe('peanut butter');
});

一定要返回断言—如果省略此 return 语句,则测试将在从 fetchData 返回的 Promise 得到解决之前完成,然后()有机会执行回调。

如果你预计 Promise 会被拒绝,请使用 .rejects 匹配器。 它的工作原理与 .resolves 匹配器类似。 如果 promise 兑现,测试将自动失败。

test('the fetch fails with an error', () => {
return expect(fetchData()).rejects.toMatch('error');
});

这些形式都不是特别优于其他形式,你可以在代码库甚至单个文件中混合和匹配它们。 这仅取决于你认为哪种风格可以让你的测试更简单。