用 Storybook 搭配測試方法,從元件開發到完成整個專案,可以增加開發者的信心,不會擔心改A錯B的狀況發生,因為從邏輯面及UI面都能被完整的測試到。
單元測試可以驗證元件的邏輯功能,驗證元件的Outup結果在給定固定的Input情況下仍保持不變。
Vue Test Utils 是 Vue 官方提供的測試套件,它是以 Jest 為基礎。它提供二個方法讓我們在測試程式中渲染元件,一個是mount
,一個是shallowMount
差異處在於,Vue Component 常常會有子父層關係,使用mount
會真實將所有子層渲染出來;shallowMount
則只渲染該元件,不渲染子層們,可以避免重複渲染讓我們的測試變慢。
當做 Unit Test 時,建議使用 shallowMount,在做整個專案的 Test 時,則是使用 mount。
shallowMount
第一個參數是測試的 component ,第二個參數可以自行選擇是否使用,可依照 Vue Test Utils 提供的 options 放入一個 object 。在這邊我們使用propsData
做為提供給元件的props。
// tests/unit/button.spec.js
import { shallowMount } from "@vue/test-utils";
import Button from '@/stories/Button.vue';
describe("Button.vue", () => {
it("Renders button text using a label prop", () => {
const label = "Test Button";
const wrapper = shallowMount(Button, {
propsData: { label }
});
expect(wrapper.text()).toBe('Test Button');
});
});
$ npm run test:unit tests/unit/button.spec.js
如果我們想要再測試 Button 如果指定 primary 為 True,元件渲染成有 Primary Class
it("Renders the button in the primary state", () => {
const label = "Button";
const primary = true;
const wrapper = shallowMount(Button, {
propsData: { label, primary }
});
expect(wrapper.classes('storybook-button--primary')).toBe(true)
});
繼續測試 Button 如果指定 primary 為 False,元件渲染為 Secondary Class
上述兩個 test 皆使用到 propsData ,因此我們可以創立一個回傳物件的函式:factory,然後簡化我們的 Test Case。
const factory = (propsData, label = 'Button') => {
return shallowMount(Button, {
propsData: {
label,
...propsData
}
})
};
...
it("Renders the button in the primary state", () => {
const wrapper = factory({primary: true});
expect(wrapper.classes('storybook-button--primary')).toBe(true)
});
it("Renders the button in the secondary state", () => {
const wrapper = factory({primary: false});
expect(wrapper.classes('storybook-button--secondary')).toBe(true)
});
加上這個 Addon 後我們可以在 Storybook 上看到測試結果,並搭配元件當下呈現的畫面。
$ npm install --save-dev @storybook/addon-jest
當我們在跑測試的時候,結果除了呈現在 Terminal,也可以輸出成JSON的檔案。
在 package.json 加上 test:generate-output
script
// package.json
"scripts": {
"test:unit": "vue-cli-service test:unit",
"test:generate-output": "vue-cli-service test:unit --json --outputFile=.jest-test-results.json",
}
然後記得把.jest-test-results.json
加入 .gitignore
// .storybook/main.js
module.exports = {
addons: ['@storybook/addon-jest'],
};
.jest-test-results.json
的結果 使用 Decorator 加入到整個 Storybook 的設定// .storybook/preview.js
import { addDecorator } from '@storybook/vue';
import { withTests } from '@storybook/addon-jest';
import results from '../.jest-test-results.json';
addDecorator(
withTests({
results,
})
);
// stories/Button.stories.js
export default {
title: 'Example/Button',
component: MyButton,
...
parameters: {
jest: ['button.spec.js'],
},
};
為了保證修改元件,也能持續不斷的符合測試結果,我們可以在開發時期,把單元測試的指令使用 watch 模式,讓測試結果持續輸出。
$ npm run test:generate-output -- --watch
接下來修改 Button.vue,故意調整到 Primary 的結果
computed: {
classes() {
return {
'storybook-button': true,
// 原本是 this.primary 在前面加上 !
'storybook-button--primary': !this.primary,
...
};
},
}
測試結果,會抓到Primary的State沒有成功Render出來
在 Storybook 頁面也會看到 Primary Button 的顯示有問題,單元測試的結果也列在 Addon Panel 的 Test 分頁之下
當每次修改我們發現測試失敗,因為是單元測試,可以在最小的修改範圍追查失敗的結果。
修改完成後,我們可以重新跑測試,可以選擇 a 跑全部的測試,也可以選擇 f 只跑失敗的測試。
現在修改成功,Storybook上也都標示Passed通過測試
//tests/unit/TaskList.spec.js
import Vue from 'vue';
import TaskList from '@/components/TaskList.vue';
import { withPinnedTasksData } from '@/components/TaskList.stories';
import { mount } from "@vue/test-utils";
describe("TaskList.vue", () => {
it('Renders pinned tasks at the start of the list', () => {
const wrapper = mount(TaskList, {
propsData: { tasks: withPinnedTasksData }
});
const firstTaskPinned = wrapper.find('.list-item:nth-child(1).TASK_PINNED');
expect(firstTaskPinned.exists()).toBe(true)
});
});
//src/components/TaskList.stories.js
export default {
title: "TaskList",
...
parameters: {
jest: ['TaskList.spec.js'],
},
};
Unit Test 完整程式碼:https://git.io/JUwJD
單元測試是為了保證,按照預期輸入時,會有預期輸出,非常適合測試元件的功能邏輯,但是單元測試需要開發者自行撰寫測試案例,不會在所有的元件上都實作單元測試,只會對是業務重點邏輯或是重用性很高的元件程式實作。
下一章要介紹的視覺回溯測試,會使用 Chromatic 自動化工具做視覺回溯測試,這種測試方式不需要寫測試腳本,而是自動會去檢查及比較前後的變更在視覺上是否發生變化,也會列出所有被影響到的元件,開發時期做變更的信心也會更加的穩固,也更容易擁抱變化。