测试异步代码
在JavaScript中执行异步代码是很常见的。 当你有以异步方式运行的代码时,Jest 需要知道当前它测试的代码是否已完成,然后它可以转移到另一个测试。 Jest有若干方法处理这种情况。
Promise
为你的测试返回一个Promise,则Jest会等待Promise的resove状态 如果 Promise 的状态变为 rejected, 测试将会失败。
例如,有一个名为fetchData
的Promise, 假设它会返回内容为'peanut butter'
的字符串 我们可以使用下面的测试代码︰
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
and await
和 .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');
});
上述示例中,async
and await
实际上是一种基于Promise的异步语法糖。
注意
Be sure to return (or await
) the promise - if you omit the return
/await
statement, your test will complete before the promise returned from fetchData
resolves or rejects.
如果期望Promise被Reject,则需要使用 .catch
方法。 请确保添加 expect.assertions
来验证一定数量的断言被调用。 否则,一个fulfilled状态的Promise不会让测试用例失败。
test('the fetch fails with an error', () => {
expect.assertions(1);
return fetchData().catch(error => expect(error).toMatch('error'));
});
回调
If you don’t use promises, you can use callbacks. For example, let’s say that fetchData
, instead of returning a promise, expects a callback, i.e. fetches some data and calls callback(null, data)
when it is complete. 你期望返回的数据是一个字符串 'peanut butter'
默认情况下,一旦到达运行上下文底部Jest测试立即结束。 这样意味着这个测试将不能按预期工作。
// 不要这样做!!!
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
中,将 error 传递给 catch
中的 done
函数。 否则,最后控制台将显示一个超时错误失败,不能显示我们在 expect(data)
中接收的值。
注意
Jest will throw an error, if the same test function is passed a done()
callback and returns a promise. This is done as a precaution to avoid memory leaks in your tests.
.resolves
/ .rejects
您还可以使用 .resolves
匹配器在您期望的声明,Jest 会等待这一 Promise 来解决。 如果 Promise 被拒绝,则测试将自动失败。
test('the data is peanut butter', () => {
return expect(fetchData()).resolves.toBe('peanut butter');
});
一定不要忘记把整个断言作为返回值返回⸺如果你忘了return
语句的话,在 fetchData
返回的这个 promise 变更为 resolved 状态、then() 有机会执行之前,测试就已经被视为已经完成了。
如果你希望Promise返回rejected,你需要使用 .rejects
匹配器。 它和 .resolves
匹配器是一样的使用方式。 如果 Promise 被拒绝,则测试将自动失败。
test('the fetch fails with an error', () => {
return expect(fetchData()).rejects.toMatch('error');
});
上述几种异步形式没有优劣之分,你可以在你的项目或者文件中混合或单独使用他们。 这只取决于哪种形式更能使您的测试变得简单。