测试

在 Scrimba 上尝试这节课

我们主要想针对 Vuex 中的 mutation 和 action 进行单元测试。

测试 Mutation

Mutation 很容易被测试,因为它们仅仅是一些完全依赖参数的函数。这里有一个小技巧,如果你在 store.js 文件中定义了 mutation,并且使用 ES2015 模块功能默认输出了 Vuex.Store 的实例,那么你仍然可以给 mutation 取个变量名然后把它输出去:

  1. const state = { ... }
  2. // `mutations` 作为命名输出对象
  3. export const mutations = { ... }
  4. export default new Vuex.Store({
  5. state,
  6. mutations
  7. })

下面是用 Mocha + Chai 测试一个 mutation 的例子(实际上你可以用任何你喜欢的测试框架):

  1. // mutations.js
  2. export const mutations = {
  3. increment: state => state.count++
  4. }
  1. // mutations.spec.js
  2. import { expect } from 'chai'
  3. import { mutations } from './store'
  4. // 解构 `mutations`
  5. const { increment } = mutations
  6. describe('mutations', () => {
  7. it('INCREMENT', () => {
  8. // 模拟状态
  9. const state = { count: 0 }
  10. // 应用 mutation
  11. increment(state)
  12. // 断言结果
  13. expect(state.count).to.equal(1)
  14. })
  15. })

测试 Action

Action 应对起来略微棘手,因为它们可能需要调用外部的 API。当测试 action 的时候,我们需要增加一个 mocking 服务层——例如,我们可以把 API 调用抽象成服务,然后在测试文件中用 mock 服务回应 API 调用。为了便于解决 mock 依赖,可以用 webpack 和 inject-loader测试 - 图1 (opens new window) 打包测试文件。

下面是一个测试异步 action 的例子:

  1. // actions.js
  2. import shop from '../api/shop'
  3. export const getAllProducts = ({ commit }) => {
  4. commit('REQUEST_PRODUCTS')
  5. shop.getProducts(products => {
  6. commit('RECEIVE_PRODUCTS', products)
  7. })
  8. }
  1. // actions.spec.js
  2. // 使用 require 语法处理内联 loaders。
  3. // inject-loader 返回一个允许我们注入 mock 依赖的模块工厂
  4. import { expect } from 'chai'
  5. const actionsInjector = require('inject-loader!./actions')
  6. // 使用 mocks 创建模块
  7. const actions = actionsInjector({
  8. '../api/shop': {
  9. getProducts (cb) {
  10. setTimeout(() => {
  11. cb([ /* mocked response */ ])
  12. }, 100)
  13. }
  14. }
  15. })
  16. // 用指定的 mutations 测试 action 的辅助函数
  17. const testAction = (action, args, state, expectedMutations, done) => {
  18. let count = 0
  19. // 模拟提交
  20. const commit = (type, payload) => {
  21. const mutation = expectedMutations[count]
  22. try {
  23. expect(mutation.type).to.equal(type)
  24. expect(mutation.payload).to.deep.equal(payload)
  25. } catch (error) {
  26. done(error)
  27. }
  28. count++
  29. if (count >= expectedMutations.length) {
  30. done()
  31. }
  32. }
  33. // 用模拟的 store 和参数调用 action
  34. action({ commit, state }, ...args)
  35. // 检查是否没有 mutation 被 dispatch
  36. if (expectedMutations.length === 0) {
  37. expect(count).to.equal(0)
  38. done()
  39. }
  40. }
  41. describe('actions', () => {
  42. it('getAllProducts', done => {
  43. testAction(actions.getAllProducts, [], {}, [
  44. { type: 'REQUEST_PRODUCTS' },
  45. { type: 'RECEIVE_PRODUCTS', payload: { /* mocked response */ } }
  46. ], done)
  47. })
  48. })

如果在测试环境下有可用的 spy (比如通过 Sinon.JS测试 - 图2 (opens new window)),你可以使用它们替换辅助函数 testAction

  1. describe('actions', () => {
  2. it('getAllProducts', () => {
  3. const commit = sinon.spy()
  4. const state = {}
  5. actions.getAllProducts({ commit, state })
  6. expect(commit.args).to.deep.equal([
  7. ['REQUEST_PRODUCTS'],
  8. ['RECEIVE_PRODUCTS', { /* mocked response */ }]
  9. ])
  10. })
  11. })

测试 Getter

如果你的 getter 包含很复杂的计算过程,很有必要测试它们。Getter 的测试与 mutation 一样直截了当。

测试一个 getter 的示例:

  1. // getters.js
  2. export const getters = {
  3. filteredProducts (state, { filterCategory }) {
  4. return state.products.filter(product => {
  5. return product.category === filterCategory
  6. })
  7. }
  8. }
  1. // getters.spec.js
  2. import { expect } from 'chai'
  3. import { getters } from './getters'
  4. describe('getters', () => {
  5. it('filteredProducts', () => {
  6. // 模拟状态
  7. const state = {
  8. products: [
  9. { id: 1, title: 'Apple', category: 'fruit' },
  10. { id: 2, title: 'Orange', category: 'fruit' },
  11. { id: 3, title: 'Carrot', category: 'vegetable' }
  12. ]
  13. }
  14. // 模拟 getter
  15. const filterCategory = 'fruit'
  16. // 获取 getter 的结果
  17. const result = getters.filteredProducts(state, { filterCategory })
  18. // 断言结果
  19. expect(result).to.deep.equal([
  20. { id: 1, title: 'Apple', category: 'fruit' },
  21. { id: 2, title: 'Orange', category: 'fruit' }
  22. ])
  23. })
  24. })

执行测试

如果你的 mutation 和 action 编写正确,经过合理地 mocking 处理之后这些测试应该不依赖任何浏览器 API,因此你可以直接用 webpack 打包这些测试文件然后在 Node 中执行。换种方式,你也可以用 mocha-loader 或 Karma + karma-webpack在真实浏览器环境中进行测试。

在 Node 中执行测试

创建以下 webpack 配置(配置好 .babelrc测试 - 图3 (opens new window)):

  1. // webpack.config.js
  2. module.exports = {
  3. entry: './test.js',
  4. output: {
  5. path: __dirname,
  6. filename: 'test-bundle.js'
  7. },
  8. module: {
  9. loaders: [
  10. {
  11. test: /\.js$/,
  12. loader: 'babel-loader',
  13. exclude: /node_modules/
  14. }
  15. ]
  16. }
  17. }

然后:

  1. webpack
  2. mocha test-bundle.js

在浏览器中测试

  1. 安装 mocha-loader
  2. 把上述 webpack 配置中的 entry 改成 'mocha-loader!babel-loader!./test.js'
  3. 用以上配置启动 webpack-dev-server
  4. 访问 localhost:8080/webpack-dev-server/test-bundle

使用 Karma + karma-webpack 在浏览器中执行测试

详见 vue-loader documentation测试 - 图4 (opens new window)