Skip to main content
Version: 29.7

测试异步代码

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

¥It's common in JavaScript for code to run asynchronously. When you have code that runs asynchronously, Jest needs to know when the code it is testing has completed, before it can move on to another test. Jest has several ways to handle this.

Promise

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

¥Return a promise from your test, and Jest will wait for that promise to resolve. If the promise is rejected, the test will fail.

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

¥For example, let's say that fetchData returns a promise that is supposed to resolve to the string 'peanut butter'. We could test it with:

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

异步/等待

¥Async/Await

或者,你可以在测试中使用 asyncawait。要编写异步测试,请在传递给 test 的函数前面使用 async 关键字。例如,可以使用以下命令测试相同的 fetchData 场景:

¥Alternatively, you can use async and await in your tests. To write an async test, use the async keyword in front of the function passed to test. For example, the same fetchData scenario can be tested with:

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');
}
});

你可以将 asyncawait.resolves.rejects 组合起来。

¥You can combine async and await with .resolves or .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 示例使用的逻辑相同。

¥In these cases, async and await are effectively syntactic sugar for the same logic as the promises example uses.

提醒

请务必返回(或 await)promise - 如果省略 return/await 语句,你的测试将在 fetchData 返回的 promise 解析或拒绝之前完成。

¥Be sure to return (or await) the promise - if you omit the return/await statement, your test will complete before the promise returned from fetchData resolves or rejects.

如果你预计 Promise 会被拒绝,请使用 .catch 方法。确保添加 expect.assertions 以验证是否调用了一定数量的断言。否则,履行的 promise 就不会通过测试。

¥If you expect a promise to be rejected, use the .catch method. Make sure to add expect.assertions to verify that a certain number of assertions are called. Otherwise, a fulfilled promise would not fail the test.

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'

¥If you don't use promises, you can use callbacks. For example, let's say that fetchData, instead of returning a promise, expects a callback, i.e. fetches some data and calls callback(null, data) when it is complete. You want to test that this returned data is the string 'peanut butter'.

默认情况下,Jest 测试在执行结束后即完成。这意味着此测试将无法按预期进行:

¥By default, Jest tests complete once they reach the end of their execution. That means this test will not work as intended:

// 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 完成后立即完成,然后再调用回调。

¥The problem is that the test will complete as soon as fetchData completes, before ever calling the callback.

test 的另一种形式可以解决这个问题。不要将测试放在带有空参数的函数中,而是使用名为 done 的单个参数。Jest 将等到 done 回调被调用后才完成测试。

¥There is an alternate form of test that fixes this. Instead of putting the test in a function with an empty argument, use a single argument called done. Jest will wait until the done callback is called before finishing the test.

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() 从未被调用,测试将失败(出现超时错误),这正是你希望发生的情况。

¥If done() is never called, the test will fail (with timeout error), which is what you want to happen.

如果 expect 语句失败,则会抛出错误并且不会调用 done()。如果我们想在测试日志中查看失败的原因,我们必须将 expect 封装在 try 块中,并将 catch 块中的错误传递给 done。否则,我们最终会遇到不透明的超时错误,该错误不会显示 expect(data) 收到的值。

¥If the expect statement fails, it throws an error and done() is not called. If we want to see in the test log why it failed, we have to wrap expect in a try block and pass the error in the catch block to done. Otherwise, we end up with an opaque timeout error that doesn't show what value was received by expect(data).

提醒

如果相同的测试函数传递 done() 回调并返回 promise,Jest 将抛出错误。这样做是为了避免测试中出现内存泄漏。

¥Jest will throw an error, if the same test function is passed a done() callback and returns a promise. This is done as a precaution to avoid memory leaks in your tests.

.resolves / .rejects

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

¥You can also use the .resolves matcher in your expect statement, and Jest will wait for that promise to resolve. If the promise is rejected, the test will automatically fail.

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

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

¥Be sure to return the assertion—if you omit this return statement, your test will complete before the promise returned from fetchData is resolved and then() has a chance to execute the callback.

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

¥If you expect a promise to be rejected, use the .rejects matcher. It works analogically to the .resolves matcher. If the promise is fulfilled, the test will automatically fail.

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

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

¥None of these forms is particularly superior to the others, and you can mix and match them across a codebase or even in a single file. It just depends on which style you feel makes your tests simpler.