Jest重点难点 - 异步代码的测试

Jest的文档有时候晦涩难懂,中文文档又有股浓浓的机翻味,因此将自己的学习笔记整理出来,融入自己的一些理解,力争做到通俗易懂。后面还会整理函数模拟,计时器模拟等内容,希望对大家有所帮助。

回调函数

需在回调函数中调用done函数,Jest会在done函数执行结束后,结束测试。

function fetchData(fn) {
  setTimeout(() => {
    fn('Hello world')
  }, 1000);
}

test('callback', (done) => {
  function callback(data) {
    expect(data).toBe('Hello world')
    done()  // 在回调函数中记得调用done函数,不然测试用例将无法通过,显示超时错误。
  }
  fetchData(callback)
})
复制代码

如果done() 函数从未被调用,测试用例会显示超时错误!!!

下面修改一下测试用例,将expect(data).toBe('Hello world') 改为 expect(data).toBe('Hello Jest')

test('callback', (done) => {
  function callback(data) {
    expect(data).toBe('Hello Jest')
    done()
  }
  fetchData(callback)
})
复制代码

显然这是不能通过测试的,但是Jest给我们的提示却是thrown: "Exceeded timeout of 5000 ms for a test……,即超时错误。这是因为expect执行失败后,会抛出一个错误,导致后面的 done() 不再执行,从而显示超时。如果我们想知道测试用例失败的原因,需要将 expect 放入 try 中,然后将 error 传递给 catch 中的 done函数。

因此,完善的测试用例应该如下:

test('callback', done => {
  function callback(data) {
    try {
      expect(data).toBe('Hello Jest')
      done()
    } catch (error) {
      done(error)
    }
  }
  fetchData(callback)
});
复制代码

Promise

如果异步代码使用了 Promise,需要在测试用例中将Promise对象返回

function fetchData() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('Hello world')
    }, 1000);
  })
}

test('Promise', () => {
  // 一定记得加上 return,否则测试在 fetchData 执行完成之前就已经结束,随后then中的expect也不会执行,会导致超时错误!!!
  return fetchData().then(data => {
    expect(data).toBe('Hello world')
  })
})
复制代码

如果需要测试reject状态的Promise,除了需要在catch方法中执行断言外,还需要使用expect.assertions来验证是否调用了指定次数的断言。

比如,我们需要测试接口失败时返回的数据。先修改fetchData函数,通过传入的success参数来模拟接口的成功和失败。

function fetchData(success) {
  return success ? Promise.resolve('success message') : Promise.reject('error message')
}
复制代码

如果接口返回失败的情况下,此时以下的测试用例时可以通过的,这没有问题。

test('Promise rejected', () => {
  return fetchData(false).catch(error => {
    expect(error).toBe('error message')  // 测试通过
  })
})
复制代码

但是如果接口返回成功,其实测试用例也是可以通过的。这是因为接口成功的话,是不会执行catch函数的。那么 catch 里面的断言也就不会被执行,所以能通过测试。看下面的例子:

test('Promise rejected', () => {
  // 接口成功,因为不会执行catch
  return fetchData(true).catch(error => {
    expect(error).toBe('error message')  // 测试通过
  })
})
复制代码

可以这并不符合我们的预期啊,我们就是想测试接口失败时的数据,此时,我们可以使用expect.assertions(1),这样我们就可以知道,至少有一次expect被执行,也就是catch一定会被执行,那么,fetchData一定要是返回失败的,而不是成功的,因此我们要将fetchData(true)要改为fetchData(false),来确保接口是返回是失败的,这样才是严谨的测试用例。

test('Promise rejected', () => {
  expect.assertions(1) // 至少执行一次 expect
  // 需要将fetchData(true)改为fetchData(false),接口失败才能执行catch里面的断言
  return fetchData(false).catch(error => {
    expect(error).toBe('error message')  // 测试通过,
  })
})
复制代码

当测试 promise 的时候,特别是测试 catch,一定要加 expect.assertions() 确保测试代码会执行

resolves 和 rejects 匹配器

resolvesrejects匹配器可以让我们很方便的测试Promise。当然不要忘记将整个断言作为返回值返回

test('the data is success message', () => {
  // 不要忘记将整个断言作为返回值返回
  return expect(fetchData(true)).resolves.toBe('success message')
});

test('the fetch fails with an error', () => {
   // 不要忘记将整个断言作为返回值返回
  return expect(fetchData(false)).resolves.toBe('error message')
});
复制代码

async 和 await

我们也可以在测试中使用asyncawait,写异步测试用例时,可以在传递给test的函数前面加上async

test('the data is success message', async () => {
  const data = await fetchData(true)
  expect(data).toBe('success message')
});

test('the fetch fails with an error', async () => {
  expect.assertions(1)
  try {
    await fetchData(false)
  } catch (e) {
    expect(e).toBe('error message')
  }
});
复制代码

你也可以将 async and awaitresolves or rejects一起使用。

test('the data is success massage', async () => {
  await expect(fetchData(true)).resolves.toBe('success message');
});

test('the fetch fails with an error', async () => {
  await expect(fetchData(false)).rejects.toBe('error message');
});
复制代码

Guess you like

Origin juejin.im/post/7067927035233959950