测试 actions

单独地测试 actions 是非常容易的。这和单独地测试 mutations 非常之相似 — 查看 这里Vuex - Actions - 图1 查看更多。在组件上下文中测试 actions 的正确性在 这里Vuex - Actions - 图2 有所讨论。

本页中描述的测试源码可以在 这里Vuex - Actions - 图3 找到。

创建 action

我们会遵循一个通常的 Vuex 模式创建一个 action:

  1. 发起一个向 API 的异步请求
  2. 对数据进行一些处理(可选)
  3. 根据 payload 的结果 commit 一个 mutation

这里有一个 认证 action,用来将 username 和 password 发送到外部 API 以检查它们是否匹配。然后其认证结果将被用于通过 commit 一个 SET_AUTHENTICATED mutation 来更新 state,该 mutation 将认证结果作为 payload。

  1. import axios from "axios"
  2. export default {
  3. async authenticate({ commit }, { username, password }) {
  4. const authenticated = await axios.post("/api/authenticate", {
  5. username, password
  6. })
  7. commit("set_authenticated", authenticated)
  8. }
  9. }

action 的测试应该断言:

  1. 是否使用了正确的 API 端?
  2. payload 是否正确?
  3. 根据结果,是否有正确的 mutation 被 commit

让我们进行下去并编写测试,并让报错信息指引我们。

编写测试

  1. describe("authenticate", () => {
  2. it("authenticated a user", async () => {
  3. const commit = jest.fn()
  4. const username = "alice"
  5. const password = "password"
  6. await actions.authenticate({ commit }, { username, password })
  7. expect(url).toBe("/api/authenticate")
  8. expect(body).toEqual({ username, password })
  9. expect(commit).toHaveBeenCalledWith(
  10. "SET_AUTHENTICATED", true)
  11. })
  12. })

因为 axios 是异步的,为保证 Jest 等到测试完成后才执行,我们需要将其声明为 async 并在其后 await 那个 actions.authenticate 的调用。不然的话(译注:即假如不使用 async/await 而仅仅将 3 个 expect 断言放入异步函数的 then() 中)测试会早于 expect 断言完成,并且我们将得到一个常绿的 — 一个不会失败的测试。

运行以上测试会给我们下面的报错信息:

  1. FAIL tests/unit/actions.spec.js
  2. authenticate authenticated a user
  3. SyntaxError: The string did not match the expected pattern.
  4. at XMLHttpRequest.open (node_modules/jsdom/lib/jsdom/living/xmlhttprequest.js:482:15)
  5. at dispatchXhrRequest (node_modules/axios/lib/adapters/xhr.js:45:13)
  6. at xhrAdapter (node_modules/axios/lib/adapters/xhr.js:12:10)
  7. at dispatchRequest (node_modules/axios/lib/core/dispatchRequest.js:59:10)

这个错误来自 axios 的某处。我们发起了一个对 /api... 的请求,并且因为我们运行在一个测试环境中,所以并不是真有一个服务器在处理请求,这就导致了错误。我们也没有定义 urlbody — 我们将在解决掉 axios 错误后做那些。

因为使用了 Jest,我们可以用 jest.mock 容易地 mock 掉 API 调用。我们将用一个 mock 版本的 axios 代替真实的,使我们能更多地控制其行为。Jest 提供了 ES6 Class MocksVuex - Actions - 图4,非常适于 mock axios

axios 的 mock 看起来是这样的:

  1. let url = ''
  2. let body = {}
  3. jest.mock("axios", () => ({
  4. post: (_url, _body) => {
  5. return new Promise((resolve) => {
  6. url = _url
  7. body = _body
  8. resolve(true)
  9. })
  10. }
  11. }))

我们将 urlbody 保存到了变量中以便断言正确的时间端点接收了正确的 payload。因为我们不想实现真正的端点,用一个理解 resolve 的 promise 模拟一次成功的 API 调用就够了。

yarn unit:pass 现在测试通过了!

测试 API Error

咱仅仅测试过了 API 调用成功的情况,而测试所有产出的可能情况也是重要的。让我们编写一个测试应对发生错误的情况。这次,我们将先编写测试,再补全实现。

测试可以写成这样:

  1. it("catches an error", async () => {
  2. mockError = true
  3. await expect(actions.authenticate({ commit: jest.fn() }, {}))
  4. .rejects.toThrow("API Error occurred.")
  5. })

我们要找到一种强制 axios mock 抛出错误的方法。正如 mockError 变量代表的那样。将 axios mock 更新为:

  1. let url = ''
  2. let body = {}
  3. let mockError = false
  4. jest.mock("axios", () => ({
  5. post: (_url, _body) => {
  6. return new Promise((resolve) => {
  7. if (mockError)
  8. throw Error()
  9. url = _url
  10. body = _body
  11. resolve(true)
  12. })
  13. }
  14. }))

只有当一个 ES6 类 mock 作用域外的(out-of-scope)变量以 mock 为前缀时,Jest 才允许访问它(译注:查看文档Vuex - Actions - 图5)。现在我们简单地赋值 mockError = true 然后 axios 就会抛出错误了。

运行该测试给我们这些报错:

  1. FAIL tests/unit/actions.spec.js
  2. authenticate catchs an error
  3. expect(function).toThrow(string)
  4. Expected the function to throw an error matching:
  5. "API Error occurred."
  6. Instead, it threw:
  7. Mock error

成功的抛出了一个错误… 却并非我们期望的那个。更新 authenticate 以达到目的:

  1. export default {
  2. async authenticate({ commit }, { username, password }) {
  3. try {
  4. const authenticated = await axios.post("/api/authenticate", {
  5. username, password
  6. })
  7. commit("SET_AUTHENTICATED", authenticated)
  8. } catch (e) {
  9. throw Error("API Error occurred.")
  10. }
  11. }
  12. }

现在测试通过了。

改良

现在你知道如何单独地测试 actions 了。至少还有一项潜在的改进可以为之,那就是将 axios mock 实现为一个 manual mockVuex - Actions - 图6。这包含在 node_modules 的同级创建一个 __mocks__ 目录并在其中实现 mock 模块。Jest 将自动使用 __mocks__ 中的 mock 实现。在 Jest 站点和因特网上有大量如何做的例子。将本文中的测试重构为一个 manual mock 就留给读者作为练习吧。

总结

本文讨论了:

  • 使用 Jest ES6 class mocks
  • 测试一个 action 成功和失败的情况

本页中描述的测试源码可以在 这里Vuex - Actions - 图7 找到。