# Jest 前端自动化测试基础 (二)异步代码测试

上一篇文章中,介绍了 Jest 的一般套路和基本语法,这篇文章将会接着介绍异步代码的测试

# 回调函数

假如现在有一个接收一个callback函数为参数的fetchData函数,会在一段时间后调用callback,并在回调函数的参数中携带数据hello jest

有了上一节的基础,一个测试用例很快就能写出来

function fetchData(callback) {
  setTimeout(() => {
    callback('hello jest')
  }, 1000)
}
test('是否返回 hello jest', () => {
  function callback(data) {
    expect(data).toBe('hello jest')
  }
  fetchData(callback)
})
// 执行了这段测试,控制态输出测试用例通过
1
2
3
4
5
6
7
8
9
10
11
12
13
14

但这样是错误的! 默认情况下,Jest 测试一旦执行到末尾就会完成,因此上面的代码中执行到fetchData(callback)就直接结束了,回调函数中的断言语句根本就没有执行

这个问题的解决办法是,将 test 函数的第二个参数由无参回调更改为一个接收一个 done 参数的回调,Jest 会等 done 回调函数执行结束后,结束测试

test('是否返回 hello jest', (done) => {
  function callback(data) {
    expect(data).toBe('hello jest')
    done()
  }
  fetchData(callback)
})
1
2
3
4
5
6
7

若 done 函数从未被调用,测试用例执行将会失败,同时输出超时错误

Timeout - Async callback was not invoked within the 5000 ms timeout specified by jest.setTimeout.
1

# Promise

以下涉及 Promise 的部分中,Promise被处理是指 Promise 进入了 fulfilled 状态,Promise被拒绝是指 Promise 进入了 rejected 状态

如果异步代码使用了 Promise,可以直接在 test 中返回一个 Promise 对象,Jest 会等待该 Promise 被处理,如果 Promise 被拒绝,测试将自动失败

function fetchData() {
  return new Promise((resolve) => {
    setTimeout(() => resolve('hello jest'), 1000)
  })
}
test('是否返回 hello jest', () => {
  // 确保返回了 Promise 对象,否则测试在 fetchData 执行完成之前就已经结束,随后then中的函数不会执行
  return fetchData().then((data) => {
    expect(data).toBe('hello jest')
  })
})
1
2
3
4
5
6
7
8
9
10
11
12

如果要测试被拒绝的 Promise,需要使用.catch方法,同时添加expect.assertions用来验证是否调用了指定次数的断言,否则 Promise 被处理时测试会直接通过

function func(condition) {
  return condition ? Promise.resolve(0) : Promise.reject(1)
}
// 正常 catch 时没有问题
test('Promise 是否进入 rejected', () => {
  return func(false).catch((e) => expect(e).toBe(1))
})
test('Promise 是否进入 rejected', () => {
  // 确保之后的代码进行了1次断言
  expect.assertions(1) // 没有这句的话 测试可以通过
  return func(true).catch((e) => expect(e).toBe(1))
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# .resolves / .rejects 匹配器

使用 Promise 时,也可以在 expect 语句中使用.resolves匹配器,Jest 将等待此 Promise 被处理,如果 Promise 被拒绝,测试将自动失败

test('是否返回 hello jest', () => {
  // 确保返回了这句断言
  return expect(fetchData()).resolves.toBe('hello jest')
})
1
2
3
4

与之对应,可以使用.rejects测试被拒绝的 Promise

test('Promise 是否进入 rejected', () => {
  return expect(func(false)).rejects.toBe(1)
})
1
2
3

# Async/Await

也可以在测试中使用async/await,例如

function fetchData() {
  return new Promise((resolve) => {
    setTimeout(() => resolve('hello jest'), 1000)
  })
}
test('是否返回 hello jest', async () => {
  const data = await fetchData()
  expect(data).toBe('hello jest')
})
function func(condition) {
  return condition ? Promise.resolve(0) : Promise.reject(1)
}
test('Promise 是否进入 rejected', async () => {
  expect.assertions(1)
  try {
    await func(false)
  } catch (e) {
    expect(e).toBe(1)
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

也可以将async/await.resolves/.rejects匹配器组合起来使用

test('是否返回 hello jest', async () => {
  await expect(fetchData()).resolves.toBe('hello jest')
})
test('Promise 是否进入 rejected', async () => {
  await expect(func(false)).rejects.toBe(1)
})
1
2
3
4
5
6
7

# 参考资料

Jest - Testing Asynchronous Code (opens new window)