Vite 單元測試與設置

首先先建立Vite專案 Vite 安裝

在專案根目錄安裝vitest @vue/test-utils jsdom

建立專案時要加入單元測試必須在Node.js 版本必須為 14 以上;專案本身是由 Vite(2.7.10 版本以上)所建構的之外

1
2
cd 到專案
npm install -D vitest @vue/test-utils jsdom

在專案根目錄安裝vitest @vue/test-utils jsdom

jsdom:是一個可以使用類似dom元件操作方式來操作html text的工具
在 ~專案根目錄/package.json執行腳本指令新增單元測試所需要的指令
scripts加入
“test”: “vitest –environment jsdom”,(啟動單元測試指令時環境內的jsdom)

1
2
3
4
5
6
7
8
9
10
11
"scripts": {
"dev": "vite",
"build": "vue-tsc && vite build",
"test": "vitest --environment jsdom",
"preview": "vite preview"
},
"devDependencies": {
"@vue/test-utils": "^2.4.5",
"jsdom": "^24.0.0",
"vitest": "^1.5.3"
}

元件

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
//引入描述、它、期望
import { describe, it, expect } from 'vitest'
//引入測試實用程式
import { mount } from '@vue/test-utils'
//引入元件
import HelloWorld from '../HelloWorld.vue'

describe('HelloWorld', () =>
{
//1+1 應該是2
it('1 + 1 should be 2', () => {
expect(1 + 1).toBe(2)
})
//正確渲染
it('renders properly', () => {
const wrapper = mount(HelloWorld, { props: { msg: 'Hello Vitest' } })
expect(wrapper.text()).toContain('Hello Vitest')
})
//在此測試檔案中使用 jsdom
it('use jsdom in this test file', () => {
const element = document.createElement('div')
element.innerHTML = '<p>Hello, HTML!</p>'
expect(element.innerHTML).toBe('<p>Hello, HTML!</p>')
})
})

執行啟動單元測試指令

1
npm run test

單元測試設置,設定完整語法專案

新增src/test/config/新增vitest.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [
vue(),
],
test: {
// 在這裡加入測試設定:.spec.js類型都執行單元測試
include: ['**/*.spec.js'],
// 全域
globals: true,//就不需要顯示引入 vitest 測試相關的 API,讓測試看起來更乾淨
environment: 'jsdom',
},
})

執行啟動單元測試指令
必須在 ~專案根目錄/package.json執行腳本指令新增單元測試所需要的指令,
改成剛剛設定的路徑

1
2
3
"scripts": {
"test": "vitest --config ./src/test/config/vitest.config.js",
},

啟動單元測試指令

1
npm run test

得到以下

github

單元測試覆蓋

Vitest 支援兩種程式碼覆蓋率引擎,v8 和 Istanbul。
v8 使用Chrome v8 引擎中內建的程式碼覆蓋率測量。
v8 的一大優點是不需要預先檢測和轉譯。
v8 無法區分 if 語句、三元條件或 for 迴圈中的條件。它將它們全部突出顯示為條件表達式。

Istanbul是一個歷史悠久的 JavaScript 測試覆蓋率工具。
Istanbul檢測需要使用 Babel 插件實現轉譯過程。幸運的是,一切都由 Vitest 為您處理。
Istanbul的一大優勢是偵測發生在單行程式碼的層級。您應該得到非常精確的結果。只有您感興趣的程式碼才會被偵測。

安裝@vitest/coverage-v8 與vitest/coverage-istanbul指令

1
2
npm install -D @vitest/coverage-v8
npm install -D @vitest/coverage-istanbul

維測試介面

1
npm install -D @vitest/ui 

模擬 HTTP 請求

組件有串接Api時

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
GuessAge.vue
<template>
<h1 data-test="title">{{ title }}</h1>
<div class="card">
<div style="width:400px;height:130px;margin-top:20px;border-style: dotted;" >
<br>
<span>Firsthand: {{firstname}}</span> <br>
<span>Age: {{age}}</span>

</div>
<label> Enter Firstname </label><br>
<input type="text" v-model="search" style="font-size:20px;border-radius:10px; border:2px solid red"/>
<a style="width:50px;height: 50px; background:red;"
data-test="getAge"
@click="getAge">Guess Age</a>
<input type="radio" value="pop"> <label>Save my data</label>
</div>
</template>

<script setup lang="ts">
import { ref,computed } from 'vue'

const props = defineProps({
title:{type:String},
})
const search = ref<string>('');
const age = ref<string>('');
const firstname = ref<string>('');
const getAge =() => {
fetch('https://api.agify.io/?name='+ search.value)
.then(response => response.json())
.then(data => {
age.value = data.age
firstname.value = data.name
search.value=""
})
}
</script>

安裝 msw

1
npm install msw --save-dev

建立假資料
新增src/mocks/handlers.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// src/mocks/handlers.js
//模擬 HTTP 請求
import { http, HttpResponse } from 'msw'
const api = "https://api.agify.io/";
export const restHandlers = [
http.get(api, (req, res, ctx) =>
{
const query = {
age: 55,
name: "tope"
}
console.log('ctx.json',ctx.json([ query]))
return res(ctx.status(200), ctx.json([
query
]))
}),
]

攔截 http 請求
生成地方

1
2
3
4
//
npx msw init 生成模擬Api資料夾 --save
//這時候就會在 public 資料夾下建立一個 mockServiceWorker.js 檔案,裏面就有攔截 http request 的程式碼了
npx msw init ./public --save

生成模擬Api資料夾
參考資料
GuessAge測試文件
GuessAge測試文件

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
//GuessAge.spec.js
import { describe, it, expect, afterEach, beforeAll, afterAll, beforeEach } from 'vitest'
import { setupServer } from 'msw/node'
import { mount } from "@vue/test-utils";
import GuessAge from "../GuessAge.vue";
//引入Api模擬
import { restHandlers } from "../../mocks/handlers";

const server = setupServer(...restHandlers)
// 在所有測試之前啟動伺服器
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }))
// 所有測試後關閉伺服器
afterAll(() => server.close())
// 每次測試後重置處理程序“對於測試隔離很重要”
afterEach(() => server.resetHandlers())
describe('GuessAge', () =>
{
let wrapper
const title = 'Guess User Age App'
if (typeof document !== 'undefined') {
beforeEach(() =>
{
wrapper = mount(GuessAge, {
props: {
title
}
})
})
}
it("測試 GuessAge 元件 props title", async () =>
{
if (typeof document !== 'undefined') {
expect(wrapper.find('[data-test="title"]').text()).toBe(title);

}
});
it("測試資料是否為函數", () =>
{
expect(typeof GuessAge.data).toBe("undefined");
});
it('快照 UI 測試', () =>
{
if (typeof document !== 'undefined') {
const wrapper = mount(GuessAge, {});
expect(wrapper.text()).toMatchSnapshot()
}
})
it("找到按鈕", () =>
{
if (typeof document !== 'undefined') {

expect(wrapper.find('[data-test="getAge"]').exists()).toBe(true);
}
});

it("按鈕點擊", async () =>
{
if (typeof document !== 'undefined') {
const ac = await wrapper.get('[data-test="getAge"]').trigger("click")
expect(wrapper.vm.search).toEqual("")
}
})
})

在元件測試中模擬 DOM API

安裝happy-dom

1
npm install happy-dom@6.0.4
1
npm i -D @vitest/coverage-istanbul

目錄

參考資料