测试异步代码
JS 中会用到很多异步编程。当测试异步代码时,Jest 需要知道代码何时结束,然后它才会继续执行另一条测试用例。Jest 有很多方法处理异步编程。
回调函数
最常见的异步编程莫过于回调函数。
比如,你有一个 fetchData(callback)
函数,获取一些数据,完成后调用 callback(data)
。你想检测返回的 data
刚好是字符串 peanut butter
。
默认情况下,Jest 一旦执行完成,测试用例就结束了。这意味着如下代码不会如你所愿:
// DO NOT do this
test('the data is peanut butter', () => {
function callback(data) {
expect(data).toBe('peanut butter 2')
}
fetchData(callback)
})
问题在于测试用例会立即结束,来不及调用回调函数。
可以使用另一种形式的 test
修复它。即在 test
回调函数中传入 done
参数。Jest 会等待 done
被执行,才会真正结束测试用例。
test('the data is peanut butter', done => {
function callback(data) {
expect(data).toBe('peanut butter')
done()
}
fetchData(callback)
})
若没有调用 done
,测试用例会失败,如你所愿。
Promises
若代码中含有 Promise,在异步测试中有更简单的方法。只需在 test 中返回一个 promise,Jest 会等待 Promise 结束。如果 Promise 被拒,测试自动失败。
比如,fetchData
使用 Promise ,成功后返回字符串 peanut butter
。我们可以如此测试:
test('the data is peanut butter', () => {
expect.assertions(1)
return fetchData().then(data => {
expect(data).toBe('peanut butter')
})
})
一定要返回 Promise --- 如果忘记 return 语句,测试就会在 fetchData
完成前结束。
如果期望使用 .catch
函数捕捉被拒的 promise,一定要使用 expect.assertions
声明期望的断言数量。否则,成功通过的 Promise 将无法使测试用例失败。
test('the fetch fails with an error', () => {
// 提前声明期望的断言数量
expect.assertions(1)
return fetchData().catch(e => expect(e).toMatch('error'))
})
.resolves
/ .rejects
Jest 20.0.0+ 有效
还可以在 expect 语句中使用 .resolves
匹配器,Jest 会等待该 promise 完成。如果它被拒,测试自动失败。
test('the data is peanut butter', () => {
expect.assertions(1)
return expect(fetchData()).resolves.toBe('peanut butter')
})
记得要返回断言 --- 如果忽略了 return
语句,测试将在 fetchData
完成前结束。
若期望 promise 被拒,可用 .rejects
匹配器,它和 .resolves
类似。如果 promise 完成,测试用例自动失败。
test('the fetch fails with an error', () => {
expect.assertions(1)
return expect(fetchData()).rejects.toMatch('error')
})
Async/Await
你也可以在测试代码中使用 async
和 await
。编写 async
测试,只需在 test 回调函数前增加 async
关键字即可。比如,同样的 fetchData
场景可以用如下代码测试:
test('the data is peanut butter', async () => {
expect.assertions(1)
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
(只在 Jest 20.0.0+) 中有效。
test('the data is peanut butter', async () => {
expect.assertions(1)
await expect(fetchData()).resolves.toBe('peanut butter')
})
test('the fetch fails with an error', async () => {
expect.assertions(1)
await expect(fetchData()).rejects.toMatch('error')
})
如上代码中,async
/await
不过语法糖,实现了 promise 同样逻辑。
以上异步风格没有优劣之分,哪种方式让代码最简单,就选择哪种。