Jest 测试异步代码

2021-09-23 20:13 更新

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

回调

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

例如,假设你有一个 ​fetchData(callback)​ 函数,获取一些数据并在完成时调用 ​callback(data)​。 你期望返回的数据是一个字符串 ​'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. });

若 ​done()​ 函数从未被调用,测试用例会正如你预期的那样执行失败(显示超时错误)。

若 ​expect ​执行失败,它会抛出一个错误,后面的 ​done()​ 不再执行。 若我们想知道测试用例为何失败,我们必须将 ​expect ​放入 ​try ​中,将 ​error ​传递给 ​catch ​中的 ​done​函数。 否则,最后控制台将显示一个超时错误失败,不能显示我们在 ​expect(data)​ 中接收的值。

Promise

如果你的代码使用了Promise​,那么有一种更直接的方法来处理异步测试。从测试中返回一个 Promise,Jest将等待该 Promise 得到解决。如果 Promise 被拒绝,则测试将自动失败。

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

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

一定不要忘记把 promise 作为返回值⸺如果你忘了 ​return ​语句的话,在 ​fetchData ​返回的这个 promise 被 resolve、then() 有机会执行之前,测试就已经被视为已经完成了。

如果你期望的 Promise 被拒绝,请使用​.catch​方法。 请确保添加 ​expect.assertions​ 来验证一定数量的断言被调用。 否则,履行的 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

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

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

一定不要忘记把整个断言作为返回值返回⸺如果你忘了​return​语句的话,在 ​fetchData ​返回的这个 promise 变更为 resolved 状态、then() 有机会执行之前,测试就已经被视为已经完成了。

如果你期望 Promise 被拒绝,请使用.rejects匹配器。它参照工程 ​.resolves​ 匹配器。 如果 Promise 被拒绝,则测试将自动失败。

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

异步/等待

或者,你也可以在测试中使用 ​async​ 和 ​await​。 要编写异步测试,请在传递给​test​的函数前面使用​async​关键字。.例如,可以用来测试相同的 ​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. });

你可以将​async​和​await​与​.resolves​或结合​.rejects​使用​​。

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

在这些情况下,对于与 promise 示例使用的相同逻辑​async​,​await​它们是有效的语法糖。

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


以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号