测试异步代码
在 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
或者,您可以在测试中使用 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'));
});
回调
如果您不使用 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');
});
这些形式中没有一种特别优于其他形式,您可以将它们混合匹配到整个代码库中,甚至在一个文件中。这取决于您认为哪种风格使您的测试更简单。