0%

最近嘗試在專案內加入測試,詢問朋友後建議先從 utils 中共用的邏輯函式開始,而在共用的邏輯函式中,會有幾個是 i18n 相關的函式。
因為頁面中常會用到將數值,轉換為顯示在畫面上的文字,如下:

1
2
3
4
5
6
7
8
9
10
11
12
const getType = (type: number) => {
switch (type) {
case 0:
return t('Type.Auto')
case 1:
return t('Type.Manual')
case 2:
return t('Type.Mix')
default:
return null
}
}

在進行測試時,總想說是否要先選定一語系,如中文\英文,然後確認他轉換的是否為該文字
但是經過嘗試後,覺得或許不應該是檢查轉換後的文字,應該要以對應 key 來檢查。
參考 overflow

  • 因此專案是使用 quasar,所以在 i18n.t 方法,引入的位置是 boot/i18n.ts
  • 在測試中,可以執行測試函式,帶入對應數值,並檢查是否回傳對應的 key
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

// 在測試中覆寫模擬 i18n.t 方法
vi.mock('../../../src/boot/i18n', () => ({
i18n: {
global: {
t: (key: string) => key, // 提供模擬的 t 函式
locale: 'en' // 假設默認的 locale 值
}
}
}))

describe('getType', () => {
it('should return the correct string for type 0', () => {
expect(getTradingType(0)).toBe('Type.Auto')
})

it('should return the correct string for type 1', () => {
expect(getTradingType(1)).toBe('Type.Manual')
})

it('should return the correct string for type 2', () => {
expect(getTradingType(2)).toBe('Type.Mix')
})
it('should return null for an unknown type', () => {
expect(getTradingType(999)).toBeNull()
})
})

小結上述方式:

使用全局模擬,vi.mock 全局模擬 i18n.t 方法,並提供模擬的 t 函式,這樣就可以在測試中,直接檢查是否回傳對應的 key,而不用檢查轉換後的文字。

另外,在跨頁面也會有共用的下拉選單,將此下拉選單的選項,也提取出來,並進行測試。

1
2
3
4
5
6

const directionOpt = computed(() => ([
{ label: t('Shared.right'), value: 0 },
{ label: t('Shared.wrong'), value: 1 }
]))

原本也想是否直接檢查 key 是否正確即可。但基於困惑,就嘗試丟 chatGpt 詢問

Read more »

在 Youtube 看到 Kevin Powell 介紹使用 grid 來作為 wrapper,以自適應網頁的縮放。

首先基本常用 grid 方式

  • 創建一個包含三欄等寬的網格布局
1
2
3
4
.content-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
}

加入使用 using named grid lines,定義出欄的 content 範圍

1
2
3
4
.content-grid {
display: grid;
grid-template-columns: 1fr [content-start] 1fr [content-end] 1fr;
}

設計一個 class 給他,如果在 content-grid 以下的元素,會將以下的元素放在 content 之間

  • 應用到 .content-grid 內部的每一個直接子元素
1
2
3
4
5
6
7
8
.content-grid {
display: grid;
grid-template-columns: 1fr [content-start] 1fr [content- end] 1fr;
}

.content-grid > * {
grid-column: content;
}

Layout grid 可以先設置:

  • 就可以看到 content 範圍
    image
    image
Read more »

因工作需求需要製作簽名面板,查看套件使用度較高 signature_pad

  1. 在專案內安裝 signature_pad 套件 npm i signature_pad
  2. 嘗試將引入的簽名套件包成可以重複使用的元件。

initializeSignaturePad : 進行初始化,如尺寸大小、筆的顏色、畫布背景色等。這邊將這些設置放在 props 讓使用時可以保有一些彈性空間。
resizeCanvas : 用於調整畫布的大小,目的是確保畫布的大小能夠適應不同的設備和螢幕解析度。
saveSignature\ clearSignature: 顧名思義就是儲存、清除簽名
注意:因為將簽名功能包成元件,使用 emit 將儲存的簽名傳到外層。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
<template>
<div
class="signature-pad"
:style="{ width: props.width, height: props.height }"
>
<canvas ref="canvas"></canvas>
<slot>
<button @click="clearSignature">清除簽名</button>
<button @click="saveSignature">儲存簽名</button>
</slot>
</div>
</template>

<script setup>
import { onMounted, ref, watch, onUnmounted } from "vue";
import SignaturePad from "signature_pad";

const props = defineProps({
width: { type: String, default: "100%" },
height: { type: String, default: "300px" },
penColor: { type: String, default: "black" },
backgroundColor: { type: String, default: "white" },
options: { type: Object, default: () => ({}) },
});

const canvas = ref(null);
let signaturePad = null;

const initializeSignaturePad = () => {
if (canvas.value) {
signaturePad = new SignaturePad(canvas.value, {
...props.options,
penColor: props.penColor,
backgroundColor: props.backgroundColor,
});
}
};

const resizeCanvas = () => {
//window.devicePixelRatio 來獲取設備的像素密度,如果無法獲取到,則將ratio設為1。
const ratio = Math.max(window.devicePixelRatio || 1, 1);
//根據畫布元素的寬度和高度,乘以ratio,來設定畫布的寬度和高度。為了確保畫布在高像素密度的設備上顯示正確。
canvas.value.width = canvas.value.offsetWidth * ratio;
canvas.value.height = canvas.value.offsetHeight * ratio;
//獲取畫布的2D繪圖上下文,並使用scale方法將繪圖上下文的縮放比例設為ratio。
canvas.value.getContext("2d").scale(ratio, ratio);
signaturePad.clear(); // Clears any existing drawing
};

const emit = defineEmits(["clear", "save"]);

const saveSignature = () => {
if (signaturePad) {
const signatureImage = signaturePad.toDataURL();
emit("save", signatureImage);
}
return null;
};

const clearSignature = () => {
if (signaturePad) {
signaturePad.clear();
emit("clear"); // 發射清除事件
}
};

// Initialize and resize canvas
onMounted(() => {
initializeSignaturePad();
window.addEventListener("resize", resizeCanvas);
resizeCanvas();
});

onUnmounted(() => {
window.removeEventListener("resize", resizeCanvas);
});

// Watch for prop changes
watch(
() => [props.penColor, props.backgroundColor, props.options],
() => {
initializeSignaturePad();
},
{ deep: true }
);
</script>

參考資料:

https://github.com/WangShayne/vue3-signature

延續上次介紹常用的 Vue test util 語法,今天要再多介紹幾個。

.text().html()

1
2
3
4
5
6
7
8
9
10
11
<template>
<div class="hello">
<p>
For a guide and recipes on how to configure / customize this project,<br />
check out the
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">
vue-cli documentation
</a>
</p>
</div>
</template>

使用 html 若直接 console 出會將 template 呈現出來

1
2
3
html=> <div class="hello">
<p> For a guide and recipes on how to configure / customize this project,<br> check out the <a href="https://cli.vuejs.org" target="_blank" rel="noopener"> vue-cli documentation </a></p>
</div>

在測試當中,可以直接抓取該斷落的內容進行比對

  1. 取得該段的 html
  2. 取得該 DOM 元素,並進行 html 的比對
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { shallowMount } from "@vue/test-utils";
import HelloWorld from "./HelloWorld.vue";

describe("HelloWorld.vue", () => {
it("html & text", () => {
const wrapper = shallowMount(HelloWorld);
// console.log("html=>", wrapper.html());
// console.log("text=>", wrapper.text());
expect(wrapper.html()).toMatch(
`<a href="https://cli.vuejs.org" target="_blank" rel="noopener"> vue-cli documentation </a>`);
console.log("aLink_>", wrapper.find('a').html())
expect(wrapper.find('a').html()).toMatch(
`<a href="https://cli.vuejs.org" target="_blank" rel="noopener"> vue-cli documentation </a>`);
});
});

component 的簡易測試

如課程範例為一個卡片,裡面有一個圖片元件(ImageBox)、內文元件(Content)

1
2
3
4
5
6
<template>
<div id="CardBox">
<ImageBox />
<Content v-if="isOpenContent" />
</div>
</template>

test goal: 檢查 component 是否有隨狀態而成功渲染與否。

findComponent

判斷 component 是否有存在
首先要如何抓元件?

  1. 因頁面中是掛載組件,所以這邊使用 mount
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { mount } from "@vue/test-utils";
import CardBox from "./index.vue";
import ImageBox from "./ImageBox.vue";
import Content from "./Content.vue";

describe("CardBox.vue", () => {
it("findComponent function test", () => {
const wrapper = mount(CardBox);

expect(wrapper.findComponent(ImageBox).exists()).toBe(true);
expect(wrapper.findComponent(Content).exists()).toBe(true);
});
});

  • findComponent(‘.class’)
  • findComponent({name: a}) 組建名稱
  • findComponent({ref: ‘ref’}) 組件實體:綁在 DOM 上的 ref
  • findComponent(component) 取得組件:抓取導入的組件

findAllComponents

  • 此方式是會回傳陣列,要使用 at()
  • 不支援 ref 查找
1
expect(wrapper.findAllComponents(ImageBox).at(0).exists()).toBe(true);
1
Using find to search for a Component is deprecated and will be removed. Use findComponent instead. The find method will continue to work for finding elements using any valid selector(tag selectors (div, foo, bar), attribute selectors ([foo], [foo="bar"])...).

測試 class and attribute

可以使用在狀態的切換或是驗證資料的對錯,可以用 class 存在與否驗證。

attribute

  • 資料在渲染的時候,是否有正確的塞入到 attribute。
  • 在做資料的渲然,將拿到的內容塞入 attribute 中,如 a link 的 href, img src

範例題目延續上述使用得內文元件(Content)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<script>
import { ref } from "@vue/reactivity";

export default {
props: {
styleColor: {
type: String,
default: "red",
},
},
setup(props) {
return {
props,
};
},
};
</script>

<template>
<div class="content">
<h1 :class="['card_title', props.styleColor]">
Vue vue-test-utils
</h1>
<a href="javascript:;">click</a>
<p class="card_text">
Vue Test Utils (VTU) is a set of utility functions aimed to simplify testing Vue.js components. It provides some methods to mount and interact with Vue components in an isolated manner!
</p>
</div>
</template>

test goal: 測試 <h1> 有沒有存在 styleColor

  • 使用 class() 帶入參數,就會去檢查該 class 是否存在
    測試架構:
  1. 先確定該元件有被渲染出來
  2. 找到 h1 tag 並查看是否有 styleColor 傳入
  3. 元件內的 a link 放入 href
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { shallowMount } from "@vue/test-utils";
import Content from "./Content.vue";

describe("Content.vue", () => {
it("find component",()=>{
const wrapper = shallowMount(Content);

expect(wrapper.find('.card_title').exists()).toBe(true)
})
it("check h1 tag have red class", ()=>{
const wrapper = shallowMount(Content);

expect(wrapper.find('h1').classes('red')).toBe(true);
})
it("button is disabled attr", () => {
const wrapper = shallowMount(Content);
console.log('red', wrapper.find('a').attributes("href"))
expect(wrapper.find('a').attributes("href")).toBe("javascript:;");
})
});

練習方式,聽完講師分享的操作之後,寫下測試目標的結構,就可以嘗試自己撰寫看看!

檢查是否可見 isVisible & exists

前面的範例練習,就可以看檔有使用 exists
在 vue template 中,有使用 v-if, v-show 來處理 div 顯示與否。
v-show 會存在於 DOM 上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { shallowMount } from "@vue/test-utils";
import PhotoItem from "./index.vue";

describe("PhotoItem.vue", () => {
it("DOM is v-if or v-show hide", () => {
const wrapper = shallowMount(PhotoItem);

// exists 用來查看是否存在
expect(wrapper.find("#box1").exists()).toBe(false);
// isVisible 用來判斷DOM是否給 v-show 隱藏起來
expect(wrapper.find("#box2").isVisible()).toBe(false);
});

});

implicit assertion(隱含斷言)

常用的 should 關鍵字:

  1. be.visible:確保元素在畫面上可見。
  2. be.hidden:確保元素在畫面上隱藏。
  3. be.checked:確保複選框或單選框元素被選中。
  4. be.disabled:確保元素被禁用。
  5. have.text:檢查元素的文字內容是否符合預期。
  6. have.value:檢查輸入元素的值是否符合預期。
  7. have.attr:檢查元素的特定屬性值是否符合預期。
  8. have.class:檢查元素是否具有特定的類名。
  9. contain:檢查元素是否包含指定的文字內容。

可根據具體需求進行選擇和使用
keywords

這裡的練習範例,驗證當前網址(URL)的斷言方法。

include:確保當前網址包含特定的子字串。

1
2
cy.url().should('include', '/login');

eq:確保當前網址與預期值完全相等。

1
2
cy.url().should('eq', 'https://example.com/dashboard');

contain:檢查當前網址是否包含特定的字串。

1
cy.url().should('contain', 'example.com');

這些斷言方法可用於驗證當前網址是否符合預期,從而確保導航或操作正確導致了預期的網址變化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
describe("Assertion practice",()=>{

it("explicit", ()=>{

cy.visit('https://opensource-demo.orangehrmlive.com/web/index.php/auth/login')

//should + keyword end

cy.url().should('include', 'orangehrmlive.com')

cy.url().should('eq', 'https://opensource-demo.orangehrmlive.com/web/index.php/auth/login')

cy.url().should('contain', 'orangehrmlive')


})

})

  • 可以看到上述,有連續使用到 url()
1
2
3
4
5
cy.url().should('include', 'orangehrmlive.com')

.should('eq', 'https://opensource-demo.orangehrmlive.com/web/index.php/auth/login')

.should('contain', 'orangehrmlive')
1
2
3
4
5
cy.url().should('include', 'orangehrmlive.com')

.and('eq', 'https://opensource-demo.orangehrmlive.com/web/index.php/auth/login')

.and('contain', 'orangehrmlive')

另外可以檢查進到頁面 logo 圖示的顯示

1
2
3
4
5
6
7
8
9
10

describe("Assertion practice",()=>{
it("explicit", ()=>{
cy.visit('https://opensource-demo.orangehrmlive.com/web/index.php/auth/login')
//check logo 呈現與否
cy.get('.orangehrm-login-branding > img').should('be.visible')
.and('exist')
})
})

  • 使用者輸入框
1
2
3
4
5
6
7
8
9
10
11
12
13
14

describe("Assertion practice",()=>{
it("explicit", ()=>{
cy.visit('https://opensource-demo.orangehrmlive.com/web/index.php/auth/login')

//檢查 username 得輸入
//取得 username 的輸入框,並且確保輸入的值
cy.get("input[placeholder='Username']").type("Admin")
cy.get("input[placeholder='Username']").should("have.value", "Admin")


})
})

explicit assertion

(顯式斷言)」是指明確使用斷言方法來檢查特定的條件或預期結果。
與隱含斷言不同,顯式斷言需要您明確指定斷言方法來進行驗證。

有空就找找實作影片來練習一下~

而今天的實作練習,訪問 momoshop 取得搜尋框元素 -> 輸入搜尋的關鍵字(“雨傘”) -> 點擊搜尋 ->
查看搜尋後的項目是否包含關鍵字。

1
2
3
4
5
6
7
8
9
10
11
12
describe('CSSLocators',() => {
it("csslocators", ()=>{
cy.visit("https://www.momoshop.com.tw/main/Main.jsp?momo=1&gclid=CjwKCAjwpuajBhBpEiwA_ZtfhU9MqvxnVs7Kh8LTNDbmKzFsmDxY16QIaP7lci98eVgfRoNz8ychuRoCao0QAvD_BwE")

//尋找某些關鍵字
cy.get('[name="keyword"]').type("雨傘")
//點擊搜尋按鈕
cy.get("[title = '搜尋']").click() //attribute
cy.get('.prdName').contains('傘')
cy.get('.prdName').contains('羽絨') //Assertion
})
})

在此文章主要針對取得 css Locator 進行系列操作。
可以使用 #idName, .cssName, [title = '搜尋'], [name = 'search'] 等方式
其實 cypress 官方網站也滿容易讀,可以根據我們想要在網頁上進行何種操作,搜尋相應的功能。
例如: Assertion cypress-assertions

小補充另一個取得定位的方式:
XPath:
XPath是一種用於導航XML文件的語言,同樣也適用於HTML文件。它提供了一種根據元素在文檔層次結構中的路徑選擇元素的方法。XPath表達式非常強大且靈活,可以根據元素的屬性、文本內容等條件進行選擇。

在Cypress中,您可以使用XPath選擇器,只需將選擇器字符串以 xpath 為前綴添加到命令中。例如:

1
cy.xpath('//button[@id="submit-btn"]').click();

每次在交付完成的任務給 PM 之前,總是會先自行反覆點擊測驗過才提能安心提供。但除了自己點擊之外,是否有其他更為簡易的方式可以進行測試呢?Cypress 是一個用於編寫端對端測試的 JavaScript 測試框架。他可以幫助我們做到基礎的自動化測試,今天就先來認識看看吧~

建立專案

在本地建立一個 cypress-project 資料夾,進入該檔案夾
npm init -y
npm install cypress

  • 安裝完後
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    {
    "name": "cypress-project-1",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "dependencies": {
    "cypress": "^12.11.0"
    }
    }

實作測試功能

  • 訪問 google 頁面
  • 進行關鍵字搜尋

測試語法

1
2
3
4
5
6
7
8
it('google search', () => {
//visit 進入不同網站
cy.visit('https://www.google.com.tw/?hl=zh_TW')

cy.get('#APjFqb').type('what is cypress.io{Enter}')
//除了點擊搜尋按鈕之外,一般也會按下 enter鍵觸發
// cy.contains('Google 搜尋').click()
})

測試操作

npx cypress open 進入測試介面,選擇 E2E

  • 選擇要測試的檔案:

  • 透過選取器可以找到相對應的元素,並針對該元素下指令
    • 這邊我們就是拿到 搜尋框,並進行關鍵字的輸入

測試結果呈現

  • 會進到 google 頁面
  • 在輸入框輸入 what is cypress.io ,點擊 Enter 搜尋

小結:

cy.visit(url):訪問指定網址。
cy.get():使用 cy.get() 方法選擇元素。我們可以使用不同的選擇器(如:CSS 選擇器)來指向特定元素。
type(): 使用 type() 方法在輸入框中輸入內容。
click():使用 cy.get() 定位到這些元素,然後使用 click() 方法模擬點擊操作。

  • 測試 App.vue
    • 引入 @vue/test-utils 使用 shallowMount 方法
    • shallowMount 可以渲染出組件

      shallowMount , mount

      shallowMount : 只會渲染該元件當層的資料內容
      mount : 深度渲染,會將元件內所包含的其他元件一起都渲染出
  • 例如以下範例: 在 App.vue 中有包含數個元件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 //App.vue
<template>

<img class="logo" alt="Vue logo" src="./assets/logo.png" />

<h1>Test demo</h1>

<AddCount />

<HelloWorld />

<div class="itemFlex">

<CardBox v-for="i in 4" :key="i" />

</div>

<UserList />

<PhotoItem />

</template>
  • 測試元件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import { shallowMount, mount } from "@vue/test-utils"

import App from '@/App.vue'



describe('App.vue test', () => {

it('測試組件 1', () => {

//渲染出 App 元件

const wrapper = shallowMount(App);

console.log(wrapper.text())

expect(wrapper.text()).toMatch("Test demo")

});

it('測試組件2', () => {

const wrapper = mount(App);

console.log(wrapper.text())

expect(wrapper.text()).toMatch("若你是寫過 Vue 但沒有寫過單元測試的工程師")

})

})

get 與 find 差異

  • 抓取某個元件中 DOM 元素
  • 兩者差異在於
    • 使用 get ,如果找不到元素會報錯,並且直接中斷測試的運行
    • 使用 find 會依據 exists() 回傳布林值

<button id="add" class="add-btn" @click="add">add 按鈕</button>

  • 測試按鈕存不存在
    • exists : 驗證某元素是否存在
    • exists
1
2
3
4
5
6
7
8
9
10
11
12
13
14
describe('AddCount.vue', () => {

it('test 1 ', () => {

const wrapper = shallowMount(AddCount);

console.log(wrapper.find('.add-btn'))

expect(wrapper.find('.add-btn').exists()).toBe(true)

})


})

在情境應用

  • 在 AddCount 加入 isOpen=ref(false) 來判斷 button 是否在一開始要呈現
  • 而再測試文件中就是要測試該按鈕在一開始並不會出現

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { shallowMount } from '@vue/test-utils'

import AddCount from '@/components/AddCount.vue'



describe('AddCount.vue', () => {

it('test 1 ', () => {

const wrapper = shallowMount(AddCount);

console.log(wrapper.find('.add-btn'))
// 找不到,並且回傳 false
expect(wrapper.find('.add-btn-err').exists()).toBe(false)

})



})

find 與 findAll

以 v-for list 的範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<script>

import { ref } from "vue";

import data from "./data.json";

export default {

setup() {

const employeeItem = ref(data);

return {

employeeItem,

};

},

};

</script>



<template>

<ul class="item">

<li class="user_list" v-for="item in employeeItem" :key="item.id">

<div>

<p>員工編號: {{ item.userId }}</p>

<p>姓名: {{ item.username }}</p>

</div>

</li>

</ul>

</template>

如果是要抓元件中 list 有幾筆
wrapper.findAll('.user_list') : 他會是一個陣列資料,其中包含好幾個 DOM wrapper

注意:
TypeError: wrapper.findAll(...).text is not a function

  • 搭配使用 at() 用於找尋其中某一個物件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { shallowMount } from '@vue/test-utils'

import UserList from '@/components/UserList.vue'


describe('test UserList', () => {

it('test Dom', () => {

const wrapper = shallowMount(UserList);

// console.log(wrapper.findAll('.user_list').at(0).text())

expect(wrapper.findAll('.user_list').at(0).text()).toMatch('員工編號: 399')

})

})

另也可以測試所拿到的資料長度

  • 測試 DOM 資料長度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import { shallowMount } from '@vue/test-utils'

import UserList from '@/components/UserList.vue'


describe('test UserList', () => {

it('test Dom', () => {

const wrapper = shallowMount(UserList);

// console.log(wrapper.findAll('.user_list').at(0).text())

expect(wrapper.findAll('.user_list').at(0).text()).toMatch('員工編號: 399')

})

it('test List length', () => {

const wrapper = shallowMount(UserList);

console.log(wrapper.findAll('.user_list').length)

expect(wrapper.findAll('.user_list').length).toBe(6)

})

})

為了不斷建立以及加深自己的技能,即便目前工作上未能使用,還是可以提前做些準備,之前看到 Mike 老師推出的 vue 單元測試,就先入手~
趁近期有空先來奠定下測試的基礎,未來可以在專案內練習撰寫!

專案創建

若一開始專案沒有選擇 jest 或加入測試 要如何在現有專案加入?

  • 可以進入該專案進行安裝
    vue add unit-jest

這裡創建的專案是使用 vue-cli 創建,並且在自選項目加入 unit test

1
vue create my-project

執行 test

npm run test:unit

更改預設的測試檔案位置

component 與 測試的檔案,可以放在同一個資料夾中,以方便找尋

  1. 建立 jest.config.js 來設定 jest
  • testMatch: 指定從 src 資料夾開始找尋與 spec 相關的檔案
1
2
3
4
5
6
7
8
9

module.exports = {

  preset: "@vue/cli-plugin-unit-jest",

  testMatch: ["**/src/**/*.spec.[jt]s?(x)"],

};

**`

測試的基本架構

  • describe : 類似群組概念,可以包一個或多個相關的測試。
  • it 與 test 一樣:其內容為測試的單位,裏面撰寫測試內容
  • 第一個參數,用來表示該測試的敘述(”Test to do list”, “Test to do 1”, “Test to do 2” )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

describe("Test to do list", () => {


  it("Test to do 1", () => {

  });


  test("Test to do 2", () => {

  });

});


  • it 也可以單獨另外撰寫
1
2
3
it('這是test case',()=>{

})
  • 執行測試呈現的樣子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { shallowMount } from '@vue/test-utils'

import HelloWorld from '@/components/HelloWorld.vue'

describe('HelloWorld.vue', () => {

it('renders props.msg when passed', () => {

const msg = 'new message'

const wrapper = shallowMount(HelloWorld, {

props: { msg }

})

expect(wrapper.text()).toMatch(msg)
//
})

})
  • wrapper 是指 HelloWorld 這個 component
  • wrapper.text() : 會顯示此元件中所包含的文字
  • expect(wrapper.text()).toMatch(msg):
    • toMatch 有合乎

expext 與斷言

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21


describe("Test to do list", () => {


  it("Test to do 1", () => {

    expect(1 + 1).toBe(2);

  });


  it("Test to do 2", () => {

    expect(4 - 1).toBe(3);

  });

});


  • expect: 預期需要匹配的項目,如放入變數、component、function
  • toBe(百分之百準確): 斷言,用什麼方式來匹配,這裡使用 toBe 函式

其他備註

而官方網站已建議若要創建新專案可以使用 vite 為基礎的專案。所以新開的專案都是使用 Vitest (之後有機會再使用新專案來開看看)。
這邊因先根據教學來進行,故先使用 vue-test-utils + jest