测试依赖于 Vuex 的 Vue.js 组件

几个月前,我开始在一个要求我写很多JavaScript的项目上工作,当我写越来越多的代码时,显而易见,缺乏单元测试正在使我放缓。

从那时起,我意识到测试是我的最高优先事项,所以我开始阅读和学习. 一些选择是自然的. Karma, Mocha, Chai 和 Sinon.JS 是伟大的包裹,他们提供你可能需要的一切。

当涉及到测试与API、Vuex(https://andsky.com/tech/tutorials/vuejs-intro-to-vuex)和接口相互作用的组件时,仍然存在一个问题. 我在测试Vue组件与Vuex商店之间的相互作用时遇到了一个特定的问题。

出发点

我将使用Vue-cli为此帖子设置Vue,它将您的项目配置,最重要的是为您配置整个测试环境Vue-cli将提示您想要的设置类型。

1$ npm install -g vue-cli
2$ vue init webpack my-project
3$ cd my-project
4$ npm install

此后,您可以运行该项目,并查看是否一切都与:

1$ npm run dev

我还建议您运行测试并验证一切按预期工作:

1$ npm test run

安装Vuex

`Vuex 是 Vue.js 应用程序的状态管理模式 + 库,它作为应用程序中的所有组件的集中存储,规则确保状态只能以可预测的方式发生突变。

假设您在同一页面上有几个视图组件,并且它们需要修改相同的数据,例如您有一个任务列表,用于创建新任务和修改现有任务的模式窗口,以及显示最后三项修改任务的侧栏。

这些组件显然需要彼此进行通信,并且没有Vuex可以实现这一点,例如从孩子发送到父母发送事件,并向孩子传递证书,或者在Vue应用实例上定义数据,但这两者都有一些缺点。

这就是 Vuex进入的地方,为这些类型的问题提供一个简单而可维护的解决方案。

要安装它,导航到您的项目的根部并运行:

1$ npm install --save vuex

由于我将使用使用使用PhantomJS的默认Vue-cli Karma设置,我们需要使用Vuex的Promise polyfill来工作:

1$ npm install --save-dev es6-promise

好吧,有了这个方法,让我们有点乐趣,并将Vuex纳入我们的项目. 在src/文件夹中,添加一个商店文件夹,具有以下目录结构:

  • index.js (引导我们的商店 ) *突变类型.js
  • actions.js
  • getters.js
  • api.js ( api 服务 )
  • items.json (我们的想象 API )

Vuex 可以设置在一个文件中,但如果您处理很多突变,它就变得更难测试,如果你不熟悉 Vuex,你可能想先阅读 官方文件

 1[label src/store/index.js]
 2require('es6-promise').polyfill()
 3import Vue from 'vue/dist/vue.common.js'
 4import Vuex from 'vuex/dist/vuex.js'
 5
 6Vue.use(Vuex)
 7
 8import * as types from './mutation-types'
 9import * as actions from './actions'
10import * as getters from './getters'
11
12const state = {
13  items: []
14}
15
16const mutations = {
17  [types.setItems] (state, items) {
18    state.items = items
19  }
20}
21
22const options = {
23  state,
24  mutations,
25  actions,
26  getters
27}
28
29export default new Vuex.Store(options)
30export { options }
 1[label src/store/actions.js]
 2import * as types from './mutation-types'
 3import api from './api'
 4
 5export const setItems = ({commit}) => {
 6  api.getItems()
 7    .then((items) => {
 8      commit(types.setItems, items)
 9    })
10    .catch((err) => console.log(err))
11}
1[label src/store/mutation-types.js]
2export const setItems = 'SET_ITEMS'
1[label src/store/getters.js]
2export const items = state => {
3  return state.items
4}
 1[label src/store/api.js]
 2export default {
 3  getItems: function () {
 4    return new Promise((resolve, reject) => {
 5      // imagine we're making an API request here
 6      const response = require('./items.json')
 7      resolve(response.body)
 8    })
 9  }
10}
1[label Server Response: src/store/items.json]
2{
3  "body": [
4    "coffee",
5    "sugar",
6    "water"
7  ]
8}

现在我们有我们的基本Vuex商店设置,我们需要将其纳入我们的Vue应用实例:

 1[label src/main.js]
 2...
 3import store from './store'
 4...
 5
 6new Vue({
 7  el: '#app',
 8  store, // uses our newly created store in our Vue instance
 9  template: '<App/>',
10  components: { App }
11})

Vue 组件取决于 Vuex 状态

现在我们已经设置了Vuex商店,并将其纳入我们的Vue应用程序实例,让我们创建一个非常简单的Vue组件,使用商店状态。

 1<template>
 2  <ul>
 3    <li v-for="(item, index) in items" :key="index" class="items">
 4      {{ item }}
 5    </li>
 6  </ul>
 7</template>
 8<script>
 9  import { mapActions, mapGetters } from 'vuex'
10  export default {
11    mounted () {
12      this.setItems()
13    },
14    methods: {
15      ...mapActions(['setItems'])
16    },
17    computed: {
18      ...mapGetters(['items'])
19    }
20  }
21</script>

正如你所看到的,这里没有什么有趣的看法。我们正在从我们的商店中渲染一份项目列表。在安装的链条中,我们发送了一项操作,从API中获取数据并将其设置为状态。

用于 Items.vue 组件的编写单元测试

那么如何去测试它呢?显然,如果无法访问商店,就无法测试这个组件,所以我们需要将商店纳入我们的测试中,但我们的API呢?我们可以测试它,但有太多的变量,测试环境必须是无菌的,每次都保持相同。

创建一个新的文件 test/unit/specs/Items.spec.js 包含以下内容:

 1require('es6-promise').polyfill()
 2import Vue from 'vue/dist/vue.common.js'
 3import Vuex from 'vuex/dist/vuex.js'
 4import store from '../../../src/store'
 5import Items from '../../../src/components/Items.vue'
 6
 7describe('Items.vue', () => {
 8  it('test initial rendering with api', (done) => {
 9    const vm = new Vue({
10      template: '<div><test></test></div>',
11      store,
12      components: {
13        'test': Items
14      }
15    }).$mount()
16
17    Vue.nextTick()
18      .then(() => {
19        expect(vm.$el.querySelectorAll('.items').length).to.equal(3)
20        done()
21      })
22      .catch(done)
23  })
24})

在这个测试中,我们正在安装一个新的Vue应用程序实例,只有我们在模板中包含的组件为<test></test>。在应用程序安装后,组件是这样,在其安装的钩子中,我们发送了Vuex操作和随后的API请求。我们使用Vue.nextTick(),因为DOM更新是同步的,如果我们没有使用它,我们的测试不会通过,因为DOM不会在那个时候更新。

所以我们需要做的就是搞笑src/store/api.js服务,为此我们将使用inject-loader,所以我们需要做的第一件事就是安装它:

1$ npm install inject-loader@^2.0.0

现在我们已经安装了,我们可以重写我们的测试,并将我们的模仿API服务注入到src/store/actions.js

 1require('es6-promise').polyfill()
 2import Vue from 'vue/dist/vue.common.js'
 3import Vuex from 'vuex/dist/vuex.js'
 4import Items from '../../../src/components/Items.vue'
 5import * as types from '../../../src/store/mutation-types'
 6import * as getters from '../../../src/store/getters'
 7
 8describe('Items.vue', () => {
 9  it('test initial rendering with mock data', (done) => {
10    const actionsInjector = require('inject-loader!../../../src/store/actions')
11    const actions = actionsInjector({
12      './api': {
13        getItems () {
14          return new Promise((resolve, reject) => {
15            const arr = ['Cat', 'Dog', 'Fish', 'Snail']
16            resolve(arr)
17          })
18        }
19      }
20    })
21
22    const state = {
23      items: []
24    }
25
26    const mutations = {
27      [types.setItems] (state, items) {
28        state.items = items
29      }
30    }
31
32    const options = {
33      state,
34      mutations,
35      actions,
36      getters
37    }
38
39    const mockStore = new Vuex.Store(options)
40
41    const vm = new Vue({
42      template: '<div><test></test></div>',
43      store: mockStore,
44      components: {
45        'test': Items
46      }
47    }).$mount()
48
49    Vue.nextTick()
50      .then(() => {
51        expect(vm.$el.querySelectorAll('.items').length).to.equal(4)
52        done()
53      })
54      .catch(done)
55  })
56})

actions.js中的奇怪的内线要求基本上注入了我们的真实的actions.js文件,但允许我们阻止该文件的依赖性。该文件的下一行展示了如何使用 actionsInjector 完成。

1$ npm test run

享受所有的绿色线条!

Published At
Categories with 技术
Tagged with
comments powered by Disqus