跳至主要内容
版本: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');
});
});

Async/Await

或者,您可以在测试中使用 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 (error) {
expect(error).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(error => expect(error).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

您也可以在 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');
});

这些形式中没有一种特别优于其他形式,您可以将它们混合匹配到整个代码库中,甚至在一个文件中。这取决于您认为哪种风格使您的测试更简单。