Vue
test
Jest
撰寫測試的目的是什麼?大致可歸類以下三點
在一個你沒碰過的專案中,有一個測試包就像有一個前輩在隨時檢查你的code,確保你不會一新增功能就破壞程式碼。
當你在撰寫component testing卻發現困難重重,
那可能表示你的compoent code寫得有問題,需要重構了。
因此,撰寫測試也可以確保你的code有一定的quality。
撰寫測試可以幫助你的團隊建立好的文件,當新的成員加入時,他可以透過測試來快速了解這個component在做什麼事。
Vue app是由許多components組成,因此,最需要被測試的就是components。
每個component都會有Inputs & Outputs
Inputs
mounted()
, created()
, etc.Outputs
比如今天有一個header,如果loggedIn
是true,就display logout button
<template>
<div>
<button v-show="loggedIn">Logout</button>
</div>
</template>
<script>
export default {
data() {
return {
loggedIn: false
}
}
}
</script>
為了搞清楚哪些是我們要測試的,首先要找出這個component的inputs和outputs
Inputs
loggedIn
)
Outputs
button
)
了解什麼是不需要測試的,可以替我們省下很多不必要的工作。
只關注測試的目標是否正確產出預期的outputs,我們不在乎它的過程或是他如何做到預期的結果。
即便未來修改了測試目標內部的logic,只要inputs & outputs不變,我們就不需要擔心要重新撰寫測試。
常常會犯的錯就是測試太多東西,例如測試框架有沒有正常運作。比如說compoenent裡有props的validation,像是Number
or Object
,不需要去測試component拿到的props是否符合這個type,因為Vue會負責這項工作。
不需要測試第三方套件是否正常運作,因為通常他們都有自己的測試,如果你不相信這個套件是否會正常運作,那就不要使用它。
建立一個Vue專案,包含rotuer, vuex, test-utils and jest
package.json
{
"name": "unit-testing-vue",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"test": "vue-cli-service test:unit",
"lint": "vue-cli-service lint"
},
"dependencies": {
"core-js": "^3.6.5",
"vue": "^2.6.11",
"vue-router": "^3.2.0",
"vuex": "^3.4.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-unit-jest": "~4.5.0",
"@vue/cli-plugin-vuex": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/eslint-config-prettier": "^6.0.0",
"@vue/test-utils": "^1.0.3",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-prettier": "^3.1.3",
"eslint-plugin-vue": "^6.2.2",
"prettier": "^1.19.1",
"vue-template-compiler": "^2.6.11"
}
}
add ./components/AppHeader.vue
<template>
<div>
<button v-show="loggedIn">Logout</button>
</div>
</template>
<script>
export default {
data() {
return {
loggedIn: false
}
}
}
</script>
在tests/unit中建立測試AppHeader用的js file
./tests/unit/AppHeader.spec.js
如同前面說的,先確認測試的inputs & outputs,剛好前面已經討論過了
Inputs
loggedIn
)
Outputs
button
) - Based on the loggedIn input, is our button displayed in the DOM or notJest describe()
function將相關聯的tests集合在一起,argument放component name的string,接著一個callback function開始寫test,run test時,console會出現component name。
import AppHeader from '@/components/AppHeader.vue'
describe('AppHeader', () => {
})
根據Jest test的文件,可以這樣寫
test('a simple string that defines your test', () => {
// testing logic
}
這裡的test也可以改為it,Jest都認得
因此我們的test會長這樣
describe('AppHeader', () => {
test('if user is not logged in, do not show logout button', () => {
// test body
})
test('if a user is logged in, show logout button', () => {
// test body
})
})
在Jest中,我們使用斷言集(assertions)來判斷測試的結果和預期的結果是否相符。
用Jest的expect()
,同時也可以使用他的許多"matchers"。
e.g.
expect(theResult).toBe(true)
expect()
中,放入測試的目標,接著用matchers來判斷result是否有符合預期。
這裡使用一個常見Jest matcher toBe()
,裡頭放入預期的結果為true。使用斷言集可以讓讀code的人一目瞭然這個test的目的:we expect the result to be true.
在寫測試時,一開始最好先寫出一定會通過(或是一定不會通過)的初始code,以免往後在找test failed的原因時發現,根本是test本身就寫錯了。
describe('AppHeader', () => {
test('if a user is not logged in, do not show the logout button', () => {
expect(true).toBe(true)
})
test('if a user is logged in, show the logout button', () => {
expect(true).toBe(true)
})
})
了解什麼matcher可以用就代表了解如何寫test了,不妨花些時間研究the Jest matchers API
npm run test
確認一下結果
現在可以寫入真正的logic:
要在測試中確認button有沒有render,就必須要把AppHeader mounted,Vue Test Utils的library有許多packages可以做到類似的事,這裡我們需要import mount
import { mount } from '@vue/test-utils'
import AppHeader from '@/components/AppHeader'
describe('AppHeader', () => {
test('if user is not logged in, do not show logout button', () => {
const wrapper = mount(AppHeader) // mounting the component
expect(true).toBe(true)
})
test('if user is logged in, show logout button', () => {
const wrapper = mount(AppHeader) // mounting the component
expect(true).toBe(true)
})
})
這裡命名為wrapper的原因是,除了mounting component,Vue Test Utils還創建了一個wrapper,裡面有許多methods可以幫助測試。
為了要確認button是否有依照logged in value出現,使用wrapper中的兩個methods.find()
, .isVisible()
,.find()
可以找出component中的button,.isVisible()
可以確認button是否有呈現在component上。
test('if user is not logged in, do not show logout button', () => {
const wrapper = mount(AppHeader)
expect(wrapper.find('button').isVisible()).toBe(false)
})
第二個情況,希望button是會出現的,因此toBe(true)
test("if logged in, show logout button", () => {
const wrapper = mount(AppHeader)
expect(wrapper.find('button').isVisible()).toBe(true)
})
但這裡需要把loggedIn設為true,測試才會通過,wrapper.setData()可以幫忙改變component的props value。
test("if logged in, show logout button", () => {
const wrapper = mount(AppHeader)
wrapper.setData({ loggedIn: true }) // setting our data value
expect(wrapper.find('button').isVisible()).toBe(true)
})
現在如果開心run test 會發現竟然沒過,因為改變loggedIn value後,DOM會重新render,在還沒update完前,expect()就去找button了。
因此需要用async await來解決,確定DOM update完後,才進入expect()
test("if logged in, show logout button", async () => {
const wrapper = mount(AppHeader)
await wrapper.setData({ loggedIn: true })
expect(wrapper.find('button').isVisible()).toBe(true)
})
run test後發現vue console 建議isVisible()換成Jest-dom的toBeVisible(),用法也有點不一樣。(2020/09)
官方文件說明:https://vue-test-utils.vuejs.org/upgrading-to-v1/#isvisible
Jest-dom library:
Custom jest matchers to test the state of the DOM
安裝Jest:
npm install --save-dev @testing-library/jest-dom
用法:
import '@testing-library/jest-dom'
expect(warpper.find('your target').element).toBeVisible()
修改後的code:
import { mount } from '@vue/test-utils'
import AppHeader from '@/components/AppHeader.vue'
import '@testing-library/jest-dom'
describe('AppHeader', () => {
test('if a user is not logged in, do not show the logout button', () => {
const wrapper = mount(AppHeader)
expect(wrapper.find('button').element).not.toBeVisible()
})
test('if logged in, show logout button', async () => {
const wrapper = mount(AppHeader)
await wrapper.setData({ loggedIn: true })
expect(wrapper.find('button').element).toBeVisible()
})
})
result:
接下來要建立一個component,來測試傳入不同的props是否會產出預期的output以及模擬user interaction
src/components/RandomNumber.vue
<template>
<div>
<span>{{ randomNumber }}</span>
<button @click="getRandomNumber">Generate Random Number</button>
</div>
</template>
<script>
export default {
props: {
min: {
type: Number,
default: 1
},
max: {
type: Number,
default: 10
}
},
data() {
return {
randomNumber: 0
}
},
methods: {
getRandomNumber() {
this.randomNumber = Math.floor(Math.random() * (this.max - this.min + 1) ) + this.min;
}
}
}
</script>
首先一樣釐清要測試的input & output
Inputs
Props:
min
& max
User Interaction:
Outputs
Rendered Output (DOM)
根據以上,可以整理三種情況需要test:
randomNumber
<= 10randomNumber
<= 300build test
tests/unit/RandomNumber.spec.js
import { mount } from '@vue/test-utils'
import RandomNumber from '@/components/RandomNumber'
describe('RandomNumber', () => {
test('By default, randomNumber data value should be 0', () => {
expect(true).toBe(false);
})
test('If button is clicked, randomNumber should be between 1 and 10', () => {
expect(true).toBe(false);
})
test('If button is clicked, randomNumber should be between 200 and 300', () => {
expect(true).toBe(false);
})
})
首先讓所有test failed, pass的是剛剛的AppHeader
result:
為了確保component的default data value for randomNumber為0,一但有人更改他,就跑不過這一項測試了。
首先要mount 這個component,就可以拿到span裡的randomNumber
test('By default, randomNumber data value should be 0', () => {
const wrapper = mount(RandomNumber)
expect(wrapper.html()).toContain('<span>0</span>')
})
藉由wrapper,拿到html,並判斷是否包含span with 0。
接著模擬使用者click button 隨機產生1~10的number。
實作步驟:
find()
取得button,用trigger()
模擬click button,在這裏DOM會被update,因此要用async await來確保DOM update完後再繼續下一個動作。 test('If button is clicked, randomNumber should be between 1 and 10', async () => {
const wrapper = mount(RandomNumber)
await wrapper.find('button').trigger('click')
const randomNumber = parseInt(wrapper.find('span').element.textContent)
expect(randomNumber).toBeGreaterThanOrEqual(1)
expect(randomNumber).toBeLessThanOrEqual(10)
})
result:
只剩下最後一個test了
mount
的第二個optional argument可以傳入propsData
test('If button is clicked, randomNumber should be between 1 and 10', () => {
const wrapper = mount(RandomNumber, {
propsData: {
min: 200,
max: 300
}
})
})
剩下的code就如同前一個test
test('If button is clicked, randomNumber should be between 200 and 300', async () => {
const wrapper = mount(RandomNumber, {
propsData: {
min: 200,
max: 300
}
})
await wrapper.find('button').trigger('click')
const randomNumber = parseInt(wrapper.find('span').element.textContent)
expect(randomNumber).toBeGreaterThanOrEqual(200)
expect(randomNumber).toBeLessThanOrEqual(300)
})
result:
問題:如何測試component中的emit event?
官方Doc有說明
emitted
method 會回傳一個被wrapper emit的自定義事件(custom event)
直接用例子實作:
LoginForm.vue
<template>
<form @submit.prevent="onSubmit">
<input type="text" v-model="name" />
<button type="submit">Submit</button>
</form>
</template>
<script>
export default {
data() {
return {
name: ''
}
},
methods: {
onSubmit() {
this.$emit('formSubmitted', { name: this.name })
}
}
}
</script>
這是一個簡單的表單,點button後,會觸發submit,form綁定的onSubmit method會emit一個custom event 'formSubmitted',帶著payload user name,傳給parent component。
撰寫測試時,最好能盡量模仿end user的所有操作,這裡把user的動作一一列出:
LoginForm.spec.js
import LoginForm from '@/components/LoginForm.vue'
import { mount } from '@vue/test-utils'
describe('LoginForm', () => {
it('emits an event with a user data payload', () => {
const wrapper = mount(LoginForm)
// 1. Find text input
// 2. Set value for text input
// 3. Simulate form submission
// 4. Assert event has been emitted
// 5. Assert payload is correct
})
})
it
等同test
找到input然後set value
describe('LoginForm', () => {
it('emits an event with user data payload', () => {
const wrapper = mount(LoginForm)
const input = wrapper.find('input[type="text"]') // Find text input
input.setValue('Vin') // Set value for text input
// 3. Simulate form submission
// 4. Assert event has been emitted
// 5. Assert payload is correct
})
})
現在只有一個input,這樣寫當然沒問題,但在production-level,一個頁面可能有好幾個input,其他人也可能更改id或class name,因此用id或class name去找input也不保險,可以用test-specific attribute解決這問題
<input data-testid="name-input" type="text" v-model="name" />
test file中,就可以用data-testid去找到目標input
const input = wrapper.find('[data-testid="name-input"]')
it('emits an event with a user data payload', () => {
const wrapper = mount(LoginForm)
const input = wrapper.find('[data-testid="name-input"]')
input.setValue('Vin')
// 1. Find text input
// 2. Set value for text input
// 3. Simulate form submission
// 4. Assert event has been emitted
// 5. Assert payload is correct
})
先前有用到trigger()
來觸發click button,這裡你可能也想用一樣的方法,但有個問題,如果以後移除了button,改用keyup.enter之類其他的方法去submit form怎辦?到時test就要重新修改。因此我們直接模擬submit event
it('emits an event with a user data payload', () => {
const wrapper = mount(LoginForm)
const input = wrapper.find('[data-testid="name-input"]')
input.setValue('Vin')
wrapper.trigger('submit')
// 4. Assert event has been emitted
// 5. Assert payload is correct
})
接著就可以測試我們預期的結果:
it('emits an event with a user data payload', () => {
const wrapper = mount(LoginForm)
const input = wrapper.find('[data-testid="name-input"]') // Find text input
input.setValue('Vin') // Set value for text input
wrapper.trigger('submit') // Simulate form submission
// Assert event has been emitted
const formSubmittedCalls = wrapper.emitted('formSubmitted')
expect(formSubmittedCalls).toHaveLength(1)
})
formSubmitted是component中emit的custom event,根據文件,wrapper.emitted('formSubmitted')
會回傳一個array(也可以寫作wrapper.emitted().formSubmitted
),因此判斷array是否length === 1
console.log(formSubmittedCalls)會長這樣:[ [ { name: 'Vin' } ] ]
接著要確認這個event emit的payload是否符合user name。
target payload: formSubmittedCalls[0][0]
完整的test code:
import LoginForm from '@/components/LoginForm.vue'
import { mount } from '@vue/test-utils'
describe('LoginForm', () => {
it('emits an event with a user data payload', () => {
const wrapper = mount(LoginForm)
const input = wrapper.find('[data-testid="name-input"]') // Find text input
input.setValue('Vin') // Set value for text input
wrapper.trigger('submit') // Simulate form submission
// Assert event has been emitted
const formSubmittedCalls = wrapper.emitted('formSubmitted')
expect(formSubmittedCalls).toHaveLength(1)
// Assert payload is correct
const expectedPayload = { name: 'Vin' }
expect(formSubmittedCalls[0][0]).toMatchObject(expectedPayload)
})
})
除非你的Vue專案都只有靜態檔案,否則通常都會需要用到API Calls,在component中測試API Calls時,不希望真的發出請求給後端伺服器,因為這樣做會造成測試code和後端直接連結,容易出現不確定性,且降低測試速度。因此用Jest mock function可以模擬 fetching api calls的過程,並且提供一些methods作為測試使用。
在這個例子中,使用json-server來做後端資料庫的server,對於不複雜的資料結構來說很夠用。
./db.json
{
"message": { "text": "Hello from the db!" }
}
在根目錄中建立db.json,就可以用json-server --watch db.json
來建立一個server,用來回傳db.json裡的data。
./services/axios.js
import axios from 'axios'
export function getMessage() {
return axios.get('http://localhost:3000/message').then(response => {
return response.data
})
}
axios.js會export getMessage(),呼叫這個function,他就會用get mothod跟db.json server拿到message。
接著建立呼叫這個API call的component
MessageDisplay.vue
<template>
<p v-if="error" data-testid="message-error">{{ error }}</p>
<p v-else data-testid="message">{{ message.text }}</p>
</template>
<script>
import { getMessage } from '@/services/axios.js'
export default {
data() {
return {
message: {},
error: null
}
},
async created() {
try {
this.message = await getMessage()
} catch (err) {
this.error = 'Oops! Something went wrong.'
}
}
}
</script>
當component created
,會用getMessage()去拿到message,並呈現在view上,如果error發生,也會有對應的message。
從getMessage()
取得的response是input,output有兩種情況:
所以在test中,要做的是:
Mock a successful call to getMessage, checking that the message
is displayed
Mock a failed call to getMessage, checking that the error
is displayed
用comment寫下每一個步驟
MessageDisplay.spec.js
import MessageDisplay from '@/components/MessageDisplay'
import { mount } from '@vue/test-utils'
describe('MessageDisplay', () => {
it('Calls getMessage and displays message', async () => {
// mock the API call
const wrapper = mount(MessageDisplay)
// wait for promise to resolve
// check that call happened once
// check that component displays message
})
it('Displays an error when getMessage call fails', async () => {
// mock the failed API call
const wrapper = mount(MessageDisplay)
// wait for promise to resolve
// check that call happened once
// check that component displays error
})
})
為了mock API call,先把getMessage
從axios.js import進來。
將axios.js的路徑傳給jest.mock()
,便能mock getMessage()
,以及使用jest的mathods
import MessageDisplay from '@/components/MessageDisplay'
import { mount } from '@vue/test-utils'
import { getMessage } from '@/services/axios'
jest.mock('@/services/axios')
...
可以將jest.mock
想作:我拿了你的getMessage
function,然後回傳給你一個mocked getMessage
function
因此,當我們之後呼叫getMessage
,其實我們用的是mocked的getMessage
,並不是真的getMessage
import MessageDisplay from '@/components/MessageDisplay'
import { mount } from '@vue/test-utils'
import { getMessage } from '@/services/axios'
jest.mock('@/services/axios')
describe('MessageDisplay', () => {
it('Calls getMessage and displays message', async () => {
const mockMessage = 'Hello from the db'
getMessage.mockResolvedValueOnce({ text: mockMessage }) // calling our mocked get request
const wrapper = mount(MessageDisplay)
// wait for promise to resolve
// check that call happened once
// check that component displays message
})
})
mockResolvedValueOnce
表示:模擬一個回傳的value,讓這個mocked API call可以resolve,所以這裡我們傳入預期的text,也就是mockMessage。
到這裡為止,我們成功模擬了一個API call,又模擬了這個API call回傳的值,而且完全沒有和server有任何關聯。
如果你有注意到這裡已經先寫了async
,因為axios是asynchronous,必須確定所有mocked call return 的promise都完成resolved,才能去寫assertion,否則一定fail。
在搞懂哪裡要寫await之前,要先想:component中的getMessage
是如何被呼叫的?
MessageDisplay.vue
async created() {
try {
this.message = await getMessage()
} catch (err) {
this.error = 'Oops! Something went wrong.'
}
}
確認了是在created
階段被呼叫的,但是vue-test-utils並沒有辦法可以取得created
階段的promises,因此必須借助一個套件flush-promises,這個套件可以確保所有的promises都resolved才進行下一個task。
補充:官方文件這裡有寫測試非同步的教學
MessageDisplay.spec.js
import MessageDisplay from '@/components/MessageDisplay'
import { mount } from '@vue/test-utils'
import { getMessage } from '@/services/axios'
import flushPromises from 'flush-promises'
jest.mock('@/services/axios')
describe('MessageDisplay', () => {
it('Calls getMessage once and displays message', async () => {
const mockMessage = 'Hello from the db'
getMessage.mockResolvedValueOnce({ text: mockMessage })
const wrapper = mount(MessageDisplay)
await flushPromises()
// check that call happened once
// check that component displays message
})
})
首先,確保我們沒有對server做出多次request
it('Calls getMessage once and displays message', async () => {
const mockMessage = 'Hello from the db'
getMessage.mockResolvedValueOnce(mockMessage)
const wrapper = mount(MessageDisplay)
await flushPromises()
expect(getMessage).toHaveBeenCalledTimes(1) // check that call happened once
// check that component displays message
})
使用.toHaveBeenCalledTimes()
並傳入數字1。
接著,我們要拿到component裡,透過getMessage()
request拿到的text,然後比對看看是否和mockMessage相同。
在MessageDisplay.vue裡已經寫好讓test使用的id: data-testid="message"
MessageDisplay.spec.js
it('Calls getMessage once and displays message', async () => {
const mockMessage = 'Hello from the db'
getMessage.mockResolvedValueOnce({ text: mockMessage })
const wrapper = mount(MessageDisplay)
await flushPromises()
expect(getMessage).toHaveBeenCalledTimes(1)
const message = wrapper.find('[data-testid="message"]').element.textContent
expect(message).toEqual(mockMessage)
})
此時run test,終於過了!
接著就是測試API call failed的情況。
第一步,mocking a failed API call,和前面的test很類似
it('Displays an error when getMessage call fails', async () => {
const mockError = 'Oops! Something went wrong.'
getMessage.mockRejectedValueOnce(mockError)
const wrapper = mount(MessageDisplay)
await flushPromises()
// check that call happened once
// check that component displays error
})
不同處在於mockError以及getMessage().mockRejectValueOnce()
,既然是failed,用reject也是理所當然的。
awaiting flushPromises()後,就可以去判斷component呈現的訊息是否和mockError相同了
it('Displays an error when getMessage call fails', async () => {
const mockError = 'Oops! Something went wrong.'
getMessage.mockRejectedValueOnce(mockError)
const wrapper = mount(MessageDisplay)
await flushPromises()
expect(getMessage).toHaveBeenCalledTimes(1)
const displayedError = wrapper.find('[data-testid="message-error"]').element
.textContent
expect(displayedError).toEqual(mockError)
})
現在run test會出現fail log:
Expected number of calls: 1 Received number of calls: 2
原來是前面第一個test已經call getMessage()
,現在第二個test又呼叫一次,所幸jest有辦法清除mock actions
在jest.mock()的下方寫入解法
jest.mock('@/services/axios')
beforeEach(() => {
jest.clearAllMocks()
})
現在beforeEach
test,jest都會把所有的mocks清除,再run test便成功了。
當你想測試一個component,它的裡頭還有一個child component,且這個child component還調用了一些外部服務(像是axios),我們的目的是測試這個component,child component的事應該要另外做它自己的測試,這時候就需要把child component stubbing
為了瞭解stubbing component的概念,先建立一個MessageContainer.vue
<template>
<MessageDisplay />
</template>
<script>
import MessageDisplay from '@/components/MessageDisplay'
export default {
components: {
MessageDisplay
}
}
</script>
MessageContainer僅僅只是import MessageDisplay,這也就是前面測試過的MessageDisplay,所以當MessageContainer渲染的時候,MessageDisplay也跟著被渲染,然後調用了axios,也就是剛剛才提到的問題:不希望真的去呼叫MessageDisplay中created
hook會觸發的axios get
request。
MessageDisplay.vue
async created() {
try {
this.message = await getMessage() // Don't want this to fire in parent test
} catch (err) {
this.error = 'Oops! Something went wrong.'
}
}
所以該如何對MessageContainer測試且不會觸發child component的axios get
request?
其實在這個例子中,只有一個外部服務(or you can call module dependency),我們可以直接mock axios,就像MessageDisplay.spec.js裡寫的測試一樣,但萬一child component調用了多組外部服務,就絕對不能如法炮製。這時就會直接mock child component本身,而不去mock它的相關依賴或套件,也就是用stub,或者說一個假的、替代版本的child component。
MessageContainer.spec.js
import MessageContainer from '@/components/MessageContainer'
import { mount } from '@vue/test-utils'
describe('MessageContainer', () => {
it('Wraps the MessageDisplay component', () => {
const wrapper = mount(MessageContainer)
})
})
在這裡該如何stub掉MessageDisplay?還記得axios是在created
hook時被呼叫的,因此,我們要在MessageContainer mount MessageDisplay 前阻止這件事。
這樣一想,在mount(MessageContainer)後面插入一個argument stubs
就很合理了。(see doc here)
import MessageContainer from '@/components/MessageContainer'
import MessageDisplay from '@/components/MessageDisplay.vue'
import { mount } from '@vue/test-utils'
describe('MessageContainer', () => {
it('Wraps the MessageDisplay component', () => {
const wrapper = mount(MessageContainer, {
stubs: {
MessageDisplay: MessageDisplay
}
})
expect(wrapper.findComponent(MessageDisplay).exists()).toBe(true)
})
})
先把MessageDisplay import進來,根據文件說明,設定stubs,接著寫assertion,只要確定MessageContainer有包含MessageDisplay就好了,畢竟在這個例子中MessageContainer也只有做這件事。
可能很常看到有人使用shallowMount
,也查到文件說shallowMount
只會mount最上層component,但前面都沒有使用的原因是?
shallowMount
完全不支援這些library。