關於 Vue 組件單元測試最多見的問題就是「我究竟應該測試什麼?」前端
雖然測試過多或過少都是可能的,但個人觀察是,開發人員一般會測試過頭。畢竟,沒有人願意本身的組件未經測試從而致使應用程序在生產中崩潰。vue
在本文中,我將分享一些用於組件單元測試的指導原則,這些指導原則能夠確保在編寫測試上不會花費大量時間,可是能夠提供足夠的覆蓋率來避免錯誤。vuex
本文假設你已經瞭解 Jest 和 Vue Test Utils。bash
在學習這些指導原則以前,咱們先來熟悉下要測試的示例組件。組件名爲Item.vue ,是 eCommerce App 裏的一個產品條目。 微信
下面是組件的源碼。注意有三個依賴項:Vuex ($store
), Vue Router ($router
) 和 Vue Auth ($auth
)。app
Item.vueide
<template>
<div>
<h2>{{ item.title }}</h2>
<button @click="addToCart">Add To Cart</button>
<img :src="item.image"/>
</div>
</template>
<script>
export default {
name: "Item",
props: [ "id" ],
computed: {
item () {
return this.$store.state.find(
item => item.id === this.id
);
}
},
methods: {
addToCart () {
if (this.$auth.check()) {
this.$store.commit("ADD_TO_CART", this.id);
} else {
this.$router.push({ name: "login" });
}
}
}
};
</script>
複製代碼
下面是測試用的 spec 文件。其中,咱們將用 Vue Test Utils 「淺掛載」示例組件,所以引入了相關模塊以及咱們要測試的 Item 組件。函數
同時還寫了一個工廠函數用於生成可覆蓋的配置對象,以避免在每一個測試中都須要指定 props 和 mock 三個依賴項。 item.spec.js單元測試
import { shallowMount } from "@vue/test-utils";
import Item from "@/components/Item";
function createConfig (overrides) {
const id = 1;
const mocks = {
// Vue Auth
$auth: {
check: () => false
},
// Vue Router
$router: {
push: () => {}
},
// Vuex
$store: {
state: [ { id } ],
commit: () => {}
}
};
const propsData = { id };
return Object.assign({ mocks, propsData }, overrides);
}
describe("Item.vue", () => {
// Tests go here
});
複製代碼
對於要測試的組件,要問的第一個也是最重要的問題是「業務邏輯是什麼」,即組件是作什麼的?學習
對於這個Item.vue,業務邏輯是:
id
屬性展現條目信息ADD_TO_CART
。當你對組件作單元測試時,可將其視爲一個黑盒。方法、計算屬性等內部邏輯隻影響輸出。
所以,下一個重點是肯定組件的輸入和輸出,由於這些也是測試的輸入和輸出。
Item.vue 的輸入是:
id
屬性輸出是:
有些組件也會將表單和事件做爲輸入,觸發事件做爲輸出。
有一個業務邏輯是「若是用戶是訪客,點擊 Add to Cart 按鈕將重定向到登陸頁」。咱們來寫這個測試。
咱們經過「shallow mount」組件來編寫測試,而後找到並點擊Add to Cart 按鈕。
test("router called when guest clicks button", () => {
const config = createConfig();
const wrapper = shallowMount(Item, config);
wrapper
.find("button")
.trigger("click");
// Assertion goes here
}
複製代碼
隨後咱們會加上 assertion。
在這個測試中很容易採起的作法是在點擊按鈕後判斷路由是否跳轉到了登陸頁,好比:
import router from "router";
test("router called when guest clicks button", () => {
...
// 錯!
const route = router.find(route => route.name === "login");
expect(wrapper.vm.$route.path).toBe(route.path);
}
複製代碼
雖然這確實也能測試組件的輸出,可是它依賴於路由功能,這不該該是組件所關心的。
直接測試組件的輸出會更好,也就是調用了 $router.push
。至於路由是否最終完成了操做,這已經超出了本測試的範疇。
所以咱們能夠監聽路由的 push
方法,並斷言它是否被登陸路由對象調用。
import router from "router";
test("router called when guest clicks button", () => {
...
jest.spyOn(config.mocks.$router, "push");
const route = router.find(route => route.name === "login");
expect(spy).toHaveBeenCalledWith(route);
}
複製代碼
接下來讓咱們測試業務邏輯「若是用戶已登陸,點擊 Add to Cart 按鈕將觸發 Vuex mutation ADD_TO_CART
」。
一樣,你不須要判斷 Vuex 狀態是否更改了。要驗證這個須要另外單獨測試 Vuex store。
組件的職責只是執行 commit,所以咱們只要測試這個動做就行。
首先重寫 $auth.check
假數據讓它返回 true
(模擬登陸用戶)。而後監聽 store 的 commit
方法,並斷言點擊按鈕後被調用。
test("vuex called when auth user clicks button", () => {
const config = createConfig({
mocks: {
$auth: {
check: () => true
}
}
});
const spy = jest.spyOn(config.mocks.$store, "commit");
const wrapper = shallowMount(Item, config);
wrapper
.find("button")
.trigger("click");
expect(spy).toHaveBeenCalled();
}
複製代碼
Item 組件展現條目數據,特別是標題和圖片。或許咱們應該寫一個測試來專門檢查這些?好比:
test("renders correctly", () => {
const wrapper = shallowMount(Item, createConfig());
// Wrong
expect(wrapper.find("h2").text()).toBe(item.title);
}
複製代碼
這又是一個沒必要要的測試,由於它只是測試了 Vue 從 Vuex 中提取數據並插入到模板的能力。Vue 這個庫已經對該機制進行了測試,因此你應該依賴於它。
可是等等,若是有人不當心將title
重命名爲name
,而後忘記更新插值表達式怎麼辦?這難道不須要測試嗎?
沒錯,可是若是你像這樣來測試模板的方方面面,什麼時候纔是個頭?
測試 HTML 最好的辦法是使用快照,用來檢查總體渲染後的結果。這不只覆蓋了標題插值,還包括圖片、按鈕文本、任何 class 等。
test("renders correctly", () => {
const wrapper = shallowMount(Item, createConfig());
expect(wrapper).toMatchSnapshot();
});
複製代碼
其餘不須要測試的點還有這些:
src
屬性是否綁定到 img 元素諸如此類。
我認爲上面三個簡單的測試對這個組件來講足夠了。
組件單元測試的一個好理念是先假設測試是沒必要要的,除非被證實是必要的。
你能夠問本身如下問題:
讓咱們愉快地單元測試吧!
原文:vuejsdevelopers.com/2019/08/26/…
翻譯:1024譯站
更多前端技術乾貨盡在微信公衆號:1024譯站