测试异步代码
在 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
。 要编写异步测试,请在传递给 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');
}
});
你可以将 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(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');
});
这些形式都不是特别优于其他形式,你可以在代码库甚至单个文件中混合和匹配它们。 这仅取决于你认为哪种风格可以让你的测试更简单。