Skip to main content
Version: 25.x

测试异步代码

在JavaScript中执行异步代码是很常见的。 当你有以异步方式运行的代码时,Jest 需要知道当前它测试的代码是否已完成,然后它可以转移到另一个测试。 Jest有若干方法处理这种情况。

回调#

最常见的异步模式是回调函数。

例如,假设您有一个 fetchData(callback) 函数,获取一些数据并在完成时调用 callback(data)。 你期望返回的数据是一个字符串 'peanut butter'

默认情况下,一旦到达运行上下文底部,jest测试立即结束。 这样意味着这个测试将不能按预期工作。

// 不要这样做!
// Don't do this!
test('the data is peanut butter', () => {
function callback(data) {
expect(data).toBe('peanut butter');
}
fetchData(callback);
});

问题在于一旦fetchData执行结束,此测试就在没有调用回调函数前结束。

还有另一种形式的 test,解决此问题。 使用单个参数调用 done,而不是将测试放在一个空参数的函数。 Jest会等done回调函数执行结束后,结束测试。

test('the data is peanut butter', done => {
function callback(data) {
try {
expect(data).toBe('peanut butter');
done();
} catch (error) {
done(error);
}
}
fetchData(callback);
});

done() 函数从未被调用,测试用例会正如你预期的那样执行失败(显示超时错误)。

expect 执行失败,它会抛出一个错误,后面的 done() 不再执行。 若我们想知道测试用例为何失败,我们必须将 expect 放入 try 中,将 error 传递给 catch 中的 done函数。 否则,最后控制台将显示一个超时错误失败,不能显示我们在 expect(data) 中接收的值。

Promises#

If your code uses promises, there is a more straightforward way to handle asynchronous tests. Return a promise from your test, and Jest will wait for that promise to resolve. 如果承诺被拒绝,则测试将自动失败。 Return a promise from your test, and Jest will wait for that promise to resolve. 如果 Promise 被拒绝,则测试将自动失败。

举个例子,比方说,fetchData,使用 Promise 代替回调的话,返回值是应该解析为字符串 'peanut butter' 的 Promise 。 我们可以使用下面的测试代码︰

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

一定不要忘记把 promise 作为返回值⸺如果你忘了 return 语句的话,在 fetchData 返回的这个 promise 被 resolve、then() 有机会执行之前,测试就已经被视为已经完成了。

If you expect a promise to be rejected, use the .catch method. 请确保添加 expect.assertions 来验证一定数量的断言被调用。 请确保添加 expect.assertions 来验证一定数量的断言被调用。 Otherwise, a fulfilled promise would not fail the test.

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

.resolves / .rejects#

您还可以使用 .resolves 匹配器在您期望的声明,Jest 会等待这一 Promise 来解决。 如果 Promise 被拒绝,则测试将自动失败。

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

一定不要忘记把整个断言作为返回值返回⸺如果你忘了return语句的话,在 fetchData 返回的这个 promise 变更为 resolved 状态、then() 有机会执行之前,测试就已经被视为已经完成了。

If you expect a promise to be rejected, use the .rejects matcher. 它参照工程 .resolves 匹配器。 它参照工程 .resolves 匹配器。 如果 Promise 被拒绝,则测试将自动失败。

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

Async/Await#

或者,您可以在测试中使用 asyncawait。 To write an async test, use the async keyword in front of the function passed to test. 例如,可以用来测试相同的 fetchData 方案︰ 例如,可以用来测试相同的 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');
}
});

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

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

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. 这只取决于哪种形式更能使您的测试变得简单。