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

回调

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

例如,假设您有一个 fetchData(callback) 函数,获取一些数据并在完成时调用 callback(data)。 You want to test that this returned data is the string 'peanut butter'.

默认情况下,Jest 测试一旦执行到末尾就会完成。 那意味着该测试将不会按预期工作:

  1. // 不要这样做!
  2. test('the data is peanut butter', () => {
  3. function callback(data) {
  4. expect(data).toBe('peanut butter');
  5. }
  6. fetchData(callback);
  7. });

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

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

  1. test('the data is peanut butter', done => {
  2. function callback(data) {
  3. try {
  4. expect(data).toBe('peanut butter');
  5. done();
  6. } catch (error) {
  7. done(error);
  8. }
  9. }
  10. fetchData(callback);
  11. });

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

In case 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 try block and pass error in catch block to done. Otherwise, we end up with opaque timeout error and no knowledge of what value was received by 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. 如果承诺被拒绝,则测试将自动失败。

举个例子,如果 fetchData 不使用回调函数,而是返回一个 Promise,其解析值为字符串 'peanut butter' 我们可以这样测试:

  1. test('the data is peanut butter', () => {
  2. expect.assertions(1);
  3. return fetchData().then(data => {
  4. expect(data).toBe('peanut butter');
  5. });
  6. });

一定要返回 Promise - 如果你省略 return 语句,您的测试将在 fetchData 完成之前完成。

如果你想要 Promise 被拒绝,使用 .catch 方法。 请确保添加 expect.assertions 来验证一定数量的断言被调用。 否则一个fulfilled态的 Promise 不会让测试失败︰

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

.resolves / .rejects

仅用于jest 20.0.0+

您也可以在 expect 语句中使用 .resolves 匹配器,Jest 将等待此 Promise 解决。 如果承诺被拒绝,则测试将自动失败。

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

一定要返回承诺 - 如果你省略 return 语句,您的测试将在 fetchData 完成之前完成。

如果你想要 Promise 被拒绝,使用 .catch 方法。 它参照工程 .resolves 匹配器。 如果 Promise 被拒绝,则测试将自动失败。

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

Async/Await

或者,您可以在测试中使用 asyncawait。 To write an async test, use the async keyword in front of the function passed to test. 例如,可以用来测试相同的 fetchData 方案︰

  1. test('the data is peanut butter', async () => {
  2. const data = await fetchData();
  3. expect(data).toBe('peanut butter');
  4. });
  5. test('the fetch fails with an error', async () => {
  6. expect.assertions(1);
  7. try {
  8. await fetchData();
  9. } catch (e) {
  10. expect(e).toMatch('error');
  11. }
  12. });

你可以将 asyncawait.resolves.rejects 结合使用(Jest 20.0.0+ 中可用)。

  1. test('the data is peanut butter', async () => {
  2. expect.assertions(1);
  3. await expect(fetchData()).resolves.toBe('peanut butter');
  4. });
  5. test('the fetch fails with an error', async () => {
  6. expect.assertions(1);
  7. await expect(fetchData()).rejects.toMatch('error');
  8. });

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

上述的诸多形式中没有哪个形式特别优于其他形式,你可以在整个代码库中,甚至也可以在单个文件中混合使用它们。 这只取决于哪种形式更能使您的测试变得简单。