前言
官網雖有測試例子,但涉及較窄,遇到組件中存在異步傳參、觸發 action、獲取 state 等問題時,編寫單元測試便不知從哪下手。html
這篇文章結合實際項目,旨在解決上述問題,順便記錄寫測試文件時遇到的一些問題,但願對各位朋友有所幫助。vue
固然,最重要的問題是:爲何要寫測試?java
於我,大概就是:若是寫測試不是爲了裝逼,那將毫無心義 對寫的程序更自信吧。git
環境
- vue-cli@2.9.2 配置 Jest 測試;
-
使用插件 vue-test-utils ,提供豐富的 api ,Vue 團隊維護 。github
#npm install --save-dev vue-test-utils
-
正常狀況下,test 目錄若是像下圖同樣,那麼接下來就能夠在 spaces 文件夾裏編寫測試用例了。vuex
簡單組件實例
-
template 部分vue-cli
<div> <el-input v-model="username"></el-input> <el-input v-model="password" @keyup.enter.native="submit"></el-input> <el-button @click.native="submit" :disabled="logining"> {{ logining ? 'login...' : 'Submit' }} </el-button> </div>
-
script 部分npm
export default { name: 'Login', data () { return { username: '', password: '' } }, computed: { logining () { return this.$store.state.login } }, methods: { async submit () { const res = await this.$store.dispatch('login', { username: this.username, password: this.password }) if (res.code === 1) this.$router.push('/index') return res } } }
編寫測試用例
// login.spec.js
// 使用了 element-ui , 須要引入 import Vue from 'vue' import ElementUI from 'element-ui' import Login from '@/components/login' Vue.use(ElementUI)
mock action and state
在這個組件裏,會調用 Vuex action ,以及 state ,爲了完成測試,須要給 Vue 傳遞一個僞造的 Store 。element-ui
import Vuex from 'vuex' import { mount, createLocalVue } from 'vue-test-utils' // 建立獨立做用域 Vue ,避免影響全局 const localVue = createLocalVue() localVue.use(Vuex) descript('Login.vue', () => { let actions let state let store beforeEach(() => { state = { login: false } actions = { login: jest.fn() // mock function } store = new Vuex.Store({ state, actions }) }) })
getter, mutation 同理。
mock router
當組件中使用 $route 或者 $router 時,並不推薦安裝 Vue Router,由於安裝以後也只是在 Vue 的原型上添加 $route 和 $router 只讀屬性,這意味着僞造 $route 或 $router 都會失效。
取而代之,只需 mock $route 和 mock $router。
const $route = { path: '/some' // ...其餘屬性 } const $router = { push: jest.fn() // ... 其餘屬性 } const wrapper = mount(Login, { mocks: { $route, $router } })
測試計算屬性 logining
it('The button className should contain "is-disabled" when "loging" is true', () => { const wrapper = mount(Login, { store, localVue, mocks: { $route, $router } }) cont btn = wrapper.find('.el-button') // btn class 沒有 is-disabled expect(btn.classes()).not.toContain('is-disabled') wrapper.setComputed({ logining: true }) // 從新渲染 wrapper.update() expect(btn.classes()).toContain('is-disabled') })
submit 方法測試
在這個簡單組件中,須要測試 input 鍵盤按下,以及 button 點擊是否觸發 submit 方法。
it('Submit method shoud be called', () => { const wrapper = mount(Login, { store, localVue, mocks: { $route, $router } }) const btn = wrapper.find('.el-button') const input = wrapper.findAll('.el-input').at(1) // 第二個 // 僞造一個jest的 mock funciton const stub = jest.fn() wrapper.setMethods({ submit: stub }) btn.trigger('click') expect(stub).toBeCalled() input.trigger('keyup', { which: 13 }) // input.trigger('keyup.enter') expect(stub).toBeCalled() })
mock funcion
最簡單的 mock function 的寫法,在上文中已經寫出:jest.fn() 。
若是要指定返回內容,能夠寫成如下方式:
jest.fn(() => 'some value')
在實際應用裏,請求結果的不肯定性,以至並不能用以上方法來 mock 請求。
查閱相關資料後,發現以下方法,能夠知足一個方法,輸出不一樣結果的需求。
const myMockFn = jest .fn(() => 'default') .mockImplementationOnce(() => 'first call') .mockImplementationOnce(() => 'second call') console.log(myMockFn(), myMockFn(), myMockFn(), myMockFn()) // > 'first call', 'second call', 'default', 'default' 用於例子組件中,只需改動測試的 action 便可: actions = { login: jest .fn(() => Promise.resolve({ code: 1, message: '登陸成功', result: '' })) .mockImplementationOnce(() => Promise.resolve({ code: 0, message: '登陸失敗', result: '' })) }
編寫測試:
it ('Mock function', async () => { const wrapper = mount(Login, { store, localVue, mocks: { $route, $router } }) // 第一次調用函數時,登陸失敗 const res = await wrapper.vm.submit() expect(res.code).toBe(0) // 第二次調用函數時,登陸成功 const otherRes = await wrapper.vm.submit() expect(res.code).toBe(1) })
測試快照
jest 有一個提供快照的功能,它可以將某個狀態下的 html 結構以一個快照文件的形式存儲下來,之後每次運行快照測試的時候若是發現跟以前的快照測試的結果不一致,測試就沒法經過。
若是頁面肯定須要改變,只須要運行測試的時候加上 -u
參數,更新快照便可。
it('Has the expected html structure', () => { expect(wrapper.element).toMatchSnapshot() })
第一次運行快照時,會建立一個 __snapshots__
目錄存放快照文件。
其餘
諸如 props ,emit 的測試, vue-test-utils 上已經有詳細的例子,也就再也不重複。
這裏有測試的例子: https://github.com/j... 。