Write the first Vue.js Component Unit Test in Jest
在Jest框架中設(shè)計(jì)你的第一則Vue.js單元測試用例
Learn how to write unit tests with the official VueJS tools and the Jest framework.
學(xué)習(xí)如何運(yùn)用VueJS官方測試工具與Jset框架設(shè)計(jì)單元測試用例
vue-test-utils, the official VueJS testing library and based on avoriaz, is just around the corner. @EddYerburgh is indeed doing a very good job creating it. It provides all necessary tooling for making easy to write unit test in a VueJS application.
首先赤屋,vue-test-utils
基于avoriaz
跟狱,是VueJS官方的測試庫,集成了眾多測試工具祖能,簡化了對VueJs應(yīng)用進(jìn)行單元測試的設(shè)計(jì)。
Jest, on the other side, is the testing framework developed at Facebook, which makes testing a breeze, with awesome features such as:
另外一方面籽暇,Jest
是Facebook開發(fā)的測試框架温治,有很多清爽簡便的特性:
- Almost no config by default
- 默認(rèn)情況基本無需用戶配置相關(guān)參數(shù)
- Very cool interactive mode
- 交互模式非常酷
- Run tests in parallel
- 對用例并發(fā)測試
- Spies, stubs and mocks out of the box
- Spies, 可以提供函數(shù)調(diào)用的信息戒悠,但不會改變函數(shù)的行為
- Stubs, 與spies類似熬荆,但是會完全替換目標(biāo)函數(shù)。這使得一個被stubbed的函數(shù)可以做任何你想要的 —— 例如拋出一個異常绸狐,返回某個特定值等等卤恳。
- Mocks, 通過組合spies和stubs,使替換一個完整對象更容易寒矿。
- Built in code coverage
- 內(nèi)置可視化覆蓋率報(bào)告功能
- Snapshot testing
- 快照測試功能
- Module mocking utilities
- 實(shí)用的mocking模塊
Probably you’ve already written test without this tools, and just by using karma + mocha + chai + sinon + …, but you’ll see how much easier it can be ??.
也許你曾經(jīng)沒有用過上述工具突琳,只是用karma、mocha符相、chai拆融、sinon等設(shè)計(jì)測試用例,那么接下來你將看到顛覆性的用例設(shè)計(jì)模式啊终。
Set up a vue-test sample project
搭建一個Vue的測試項(xiàng)目
Let’s start by creating a new project using vue-cli
answering NO to all yes/no questions:
我們用vue-cli
搭設(shè)一個全新的項(xiàng)目冠息,對所有需要您回答“是否需要。孕索。□锾迹”的步驟全部選擇“不要(NO)”搞旭。
npm install -g vue-cli
vue init webpack vue-test
cd vue-test
Then we’ll need to install some dependencies:
然后我們需要安裝以下幾個必要的依賴:
# Install dependencies
npm i -D jest jest-vue-preprocessor babel-jest
jest-vue-preprocessor
is needed for making jest understand .vue
files, and babel-jest
for the integration with Babel.
jest-vue-preprocessor
是用來識別并解析VUE組件文件的工具,babel-jest
則是用來整合Babel
工具庫的橋梁菇绵。
As vue-test-utils
, it can be installed already from npm, since beta.1
has been published.
vue-test-utils
已經(jīng)發(fā)布了beta.1
版本肄渗,現(xiàn)在我們已經(jīng)可以在npm
上安裝了。
npm i -D vue-test-utils
Let’s add the following Jest configuration in the package.json
:
我們在package.json
文件中添加以下Jest的配置:
...
"jest": {
"moduleNameMapper": {
"^vue$": "vue/dist/vue.common.js"
},
"moduleFileExtensions": [
"js",
"vue"
],
"transform": {
"^.+\\.js$": "<rootDir>/node_modules/babel-jest",
".*\\.(vue)$": "<rootDir>/node_modules/jest-vue-preprocessor"
}
}
...
moduleFileExtensions will tell Jest which extensions to look for, and transform which preprocessor to use for a file extension.
moduleFileExtensions
是來告訴Jest掃描哪些類型的文件咬最;transform
設(shè)定了各個類型的文件對應(yīng)哪些預(yù)處理工具翎嫡。
At last, add a test script to the package.json:
最后我們添加一段測試腳本在package.json
的scripts
里:
{
"scripts": {
"test": "jest",
...
},
...
}
Testing a Component
對一個組件進(jìn)行測試
I’ll be using Single File Components here, and I haven’t checked if it works by splitting them in their own html, css or js files, so let’s assume you’re doing that as well.
我將會用單文件組件進(jìn)行測試,我暫時先不管它的html結(jié)構(gòu)永乌、樣式和js邏輯是否可行惑申,我先假設(shè)你已經(jīng)把他們都處理好了。
First create a MessageList.vue component under src/components:
首先先創(chuàng)建一個MessageList.vue
組件翅雏,放在src/components
目錄下:
<template>
<ul>
<li v-for="message in messages">
{{ message }}
</li>
</ul>
</template>
<script>
export default {
name: 'list',
props: ['messages']
}
</script>
And update App.vue to use it, as follows:
然后在App.vue中引入MessageList.vue
:
<template>
<div id="app">
<MessageList :messages="messages"/>
</div>
</template>
<script>
import MessageList from './components/MessageList'
export default {
name: 'app',
data: () => ({ messages: ['Hey John', 'Howdy Paco'] }),
components: {
MessageList
}
}
</script>
We have already a couple of components that we can test. Let’s create a test folder under the project root, and a App.test.js:
我們已經(jīng)有了一對組件可以進(jìn)行測試圈驼。然后我們在項(xiàng)目根目錄下創(chuàng)建一個測試文件夾和一個App.test.js
文件:
import Vue from 'vue'
import App from '../src/App'
describe('App.test.js', () => {
let cmp, vm
beforeEach(() => {
cmp = Vue.extend(App) // Create a copy of the original component
vm = new cmp({
data: { // Replace data value with this fake data
messages: ['XXX']
}
}).$mount() // Instances and mounts the component
})
it('equals messages to ["XXX"]', () => {
expect(vm.messages).toEqual(['XXX'])
})
})
Right now, if we run npm test (or npm t as a shorthand version), the test should run and pass. Since we’re modifying the tests, let’s better run it in watch mode:
現(xiàn)在我們在命令行執(zhí)行npm test
(或短命令npm run t
),可以看到用例已經(jīng)被執(zhí)行望几,而且已經(jīng)通過了測試绩脆。如果想觀察修改用例會有什么效果,可以執(zhí)行npm t --watch
命令。
npm t -- --watch
The problem with nested components
測試過程中要注意的嵌套組件問題
This test is too simple. Let’s check that the output is the expected as well. For that we can use the amazing Snapshots feature of Jest, that will generate a snapshot of the output and check it against in the upcoming runs. Add after the previous it in App.test.js:
這個測試有些過于簡單靴迫,我們來檢測一下輸出結(jié)果是否符合我們的預(yù)期惕味。
此時我們可以用Jest神奇的快照功能來進(jìn)行測試,此功能會產(chǎn)生一個輸出結(jié)果的快照玉锌,與接下來后續(xù)測試的結(jié)果進(jìn)行對比名挥。
我們先將一下代碼添加到App.test.js
中:
it('has the expected html structure', () => {
expect(vm.$el).toMatchSnapshot()
})
That will create a test/snapshots/App.test.js.snap file. Let’s open it and inspect it:
進(jìn)程會在__snapshots__
目錄中產(chǎn)生一個App.test.js.snap
文件。
我們打開后看到里面的代碼是:
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`App.test.js has the expected html structure 1`] = `
<div
id="app"
>
<ul>
<li>
XXX
</li>
</ul>
</div>
`;
In case you haven’t noticed, there is a big problem here: the MessageList component has been rendered as well. Unit tests must be tested as an independent unit, meaning that in App.test.js we wanna test App component and don’t care at all about anything else.
也許你沒有發(fā)現(xiàn)芬沉,這里有個問題——MessageList組件在父組件的快照中已經(jīng)被成功渲染躺同。
單元測試的理念是單個組件需要被作為獨(dú)立單元檢測,這意味著我們測試App根組件的時候丸逸,其實(shí)是不在乎其中的子組件具體是什么狀態(tài)的蹋艺。
This can be the reason of several problems. Imagine for example, that the children components (MessageList in this case) perform side effect operations on the created hook, such as calling fetch, a Vuex action or state changes? That’s something we definitely don’t want.
這會引起幾個相關(guān)問題。想象一下黄刚,像MessageList這樣的子組件捎谨,會在created
鉤子中引發(fā)副作用,比如會不會調(diào)用一些獲取數(shù)據(jù)的方法憔维、Vuex的action被調(diào)用來改變了state中的數(shù)據(jù)涛救?這些不是我們想要看到的。
Luckily, Shallow Rendering solves this nicely.
幸運(yùn)的是业扒,淺渲染可以幫我們完美解決問題检吆!
What is Shallow Rendering?
什么是淺渲染
Shallow Rendering is a technique that assures your component is rendering without children. This is useful for:
淺渲染是確保組件不受子組件影響,被獨(dú)立測試的技術(shù)程储,其用武之地包括:
- Testing only the component you want to test (that’s what Unit Test stands for)
- 只測試你想測的組件(這本身就是單元測試的初衷)
- Avoid side effects that children components can have, such as making HTTP calls, calling store actions…
- 避免子組件對父組件的影響蹭沛,如請求HTTP、store中的actions等動作
Testing a Component with vue-test-utils
運(yùn)用vue-test-utils
來對組件測試
vue-test-utils
provide us with Shallow Rendering among other features. We could rewrite the previous test as follows:
vue-test-utils
提供給我們一系列有用的特性章鲤,其中就包括淺渲染摊灭,我們可以改寫一下我們之前寫的測試用例:
import { shallow } from 'vue-test-utils'
import App from '../src/App'
describe('App.test.js', () => {
let cmp
beforeEach(() => {
cmp = shallow(App, { // Create a shallow instance of the component
data: {
messages: ['XXX']
}
})
})
it('equals messages to ["XXX"]', () => {
// Within cmp.vm, we can access all Vue instance methods
expect(cmp.vm.messages).toEqual(['XXX'])
})
it('has the expected html structure', () => {
expect(cmp.element).toMatchSnapshot()
})
})
And now, if you’re still running Jest in watching mode, you’ll see the test still pass, but the Snapshot doesn’t match. Press u to regenerate it. Open and inspect it again:
現(xiàn)在,如果你之前執(zhí)行的是監(jiān)聽模式败徊,你可以看到測試用例依然通過了帚呼,但是快照已經(jīng)報(bào)錯了,提示不匹配皱蹦。 你可以在命令行用u參數(shù)煤杀,來重新生成快照,我們現(xiàn)在看看快照代碼的內(nèi)容變成什么樣了:
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`App.test.js has the expected html structure 1`] = `
<div
id="app"
>
<!-- -->
</div>
`;
You see? Now no children have been rendered and we tested the App
component fully isolated from the component tree. Also, if you have any created
or whatever hooks in the children components, they haven’t been called either ??.
看到了嗎沪哺?現(xiàn)在沒有子組件被渲染后的代碼了怜珍。我們現(xiàn)在終于客觀上完成了對App組件進(jìn)行真正意義上的單元測試。此時無論你在子組件中任何生命周期的鉤子里有什么樣的動作凤粗,都不會被包括在此輪的測試中酥泛。
If you’re curious about how shallow render is implemented, check out the source code and you’ll see that basically is stubbing the components
key, the render
method and the lifecycle hooks.
如果你好奇淺渲染的底層邏輯是什么今豆,你可以查看到源碼中關(guān)于生命周期鉤子和相關(guān)方法的代碼。
In the same vein, you can implement the MessageList.test.js
test as follows:
同樣的方式柔袁,我們可以創(chuàng)建MessageList.test.js
文件呆躲,來對MessageList進(jìn)行測試:
import { shallow } from 'vue-test-utils'
import MessageList from '../src/components/MessageList'
describe('MessageList.test.js', () => {
let cmp
beforeEach(() => {
cmp = shallow(MessageList, {
// Beaware that props is overriden using `propsData`
propsData: {
messages: ['XXX']
}
})
})
it('has received ["XXX"] as the message property', () => {
expect(cmp.vm.messages).toEqual(['XXX'])
})
it('has the expected html structure', () => {
expect(cmp.element).toMatchSnapshot()
})
})