几个月前,我开始在一个要求我写很多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
享受所有的绿色线条!