轉自: vue 單文件測試
前言
官網雖有測試例子,但涉及較窄,遇到組件中存在異步傳參、觸發 action、獲取 state 等問題時,編寫單元測試便不知從哪下手。
這篇文章結合實際項目,旨在解決上述問題,順便記錄寫測試文件時遇到的一些問題,希望對各位朋友有所幫助。
當然,最重要的問題是:為什么要寫測試?
於我,大概就是:如果寫測試不是為了裝逼,那將毫無意義 對寫的程序更自信吧。
環境
- vue-cli@2.9.2 配置 Jest 測試;
-
使用插件 vue-test-utils ,提供豐富的 api ,Vue 團隊維護 。
#npm install --save-dev vue-test-utils -
正常情況下,test 目錄如果像下圖一樣,那么接下來就可以在 spaces 文件夾里編寫測試用例了。
簡單組件實例
-
template 部分
<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 部分
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 。
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... 。
