# Vitest ## References + 📑 [**Vitest**](https://vitest.dev/) + 🔗 [**Vue.js - Testing**](https://vuejs.org/guide/scaling-up/testing.html) + 🔗 [**IsRayNotArray - 別再用 1+1=2 學測試了!這次就讓我們從 Vitest 開始學單元測試吧!**](https://israynotarray.com/vitest/20230420/4055762937/) + 🔗 [**ShawnL - Vue.js 3 前端測試入門從這裡開始!覆蓋率(Coverage)篇**](https://ithelp.ithome.com.tw/articles/10309187) + 🔗 [**ShawnL - Vue3 單元測試!Feat. Vitest + Vue Test Utils**](https://ithelp.ithome.com.tw/users/20119062/ironman/5554) ## Regulations |🔮 <span class="important">IMPORTANT</span>| |:---| |測試檔案名稱:`{檔案名稱}.test.{副檔名}` 或 `{檔案名稱}.spec.{副檔名}`| |測試檔案一律放在 `__tests__` 目錄底下| |可以集中放也可以分開放 (和 pytest 一樣),這部分應該就是風格問題了| ## CLI ### `vitest run` 測試 ### `vitest watch` 測試 + 監聽變化模式 (同 `vitest`) ### `vitest init` + `vitest init browser` 產生 Vitest config ### `vitest list` |🚨 <span class="caution">CAUTION</span>| |:---| |尋找範圍不限於本目錄| + `vitest list HelloWorld.spec.ts` 尋找 HelloWorld.spec.ts 中的所有測試 + `vitest list -t "renders properly"` 尋找描述為 "renders properly" 的所有測試 + `vitest list HelloWorld.spec.ts -t "renders properly"` 尋找 HelloWorld.spec.ts 中,描述為 "renders properly" 的所有測試 + `vitest list HelloWorld.spec.ts -t "renders properly" --json=./file.json` 尋找 HelloWorld.spec.ts 中,描述為 "renders properly" 的所有測試,並輸出為 JSON 格式 ### `vitest --ui` 啟用 [UI 模式](https://ithelp.ithome.com.tw/articles/10309162) (需下載 `@vitest/ui`) ## Usage ### 預期結果 `expect` + 方法 + `toBe()` + `toEqual()` + `toBeGreaterThan()` + `toBeLessThan()` + [更多使用方法...][vitest: expect] + 測試案例 ```ts expect(1 + 2).toBe(3) ``` ### 一個測試 `test` | `it` + 測試案例 ```ts import { expect, test } from 'vitest' test('一個測試的描述', () => { // 測試邏輯 expect(1 + 2).toBe(3) }) ``` ### 一組測試 `describe` + 待測功能 (TypeScript) ```ts // src/utils/func.ts export function add(a: number, b: number): number { return a + b } export function sub(a: number, b: number): number { return a - b } ``` + 測試案例 ```ts // src/utils/__tests__/func.spec.ts import { describe, expect, test } from 'vitest' import { add, sub } from '../func' describe('測試四則運算', () => { // 這裡可以包含多個 test 或 it test('兩個數相加', () => { expect(add(1, 2)).toBe(3) }) test('兩個數相減', () => { expect(sub(5, 2)).toBe(3) }) }) ``` + 測試輸出 ``` ✓ src/utils/__tests__/func.spec.ts (2) ✓ 測試四則運算 (2) ✓ 兩個數相加 ✓ 兩個數相減 Test Files 1 passed (1) Tests 2 passed (2) Start at 17:19:04 Duration 32ms ``` ### 生命週期 `before~` / `after~` |🚨 <span class="caution">CAUTION</span>| |:---| |<mark>hook 調用的位置,會影響其執行位置</mark><br />(可藉由觀察範例的測試輸出發現規律)| + 函式 + `beforeAll()`:在<mark>所有</mark>測試開始<mark>之前</mark>執行 + `beforeEach()`:在<mark>每個</mark>測試開始<mark>之前</mark>執行 + `afterAll()`:在<mark>所有</mark>測試結束<mark>之後</mark>執行 + `afterEach()`:在<mark>每個</mark>測試結束<mark>之後</mark>執行 + 範例 + 測試案例 ```ts import { beforeAll, beforeEach, describe, expect, test } from 'vitest' import { add, sub } from '../func' beforeAll(() => { console.log('beforeAll') }) beforeEach(() => { console.log('beforeEach') }) describe('測試四則運算1', () => { beforeAll(() => { console.log('beforeAll in 測試四則運算1') }) beforeEach(() => { console.log('beforeEach in 測試四則運算1') }) test('兩個數相加', () => { expect(add(1, 2)).toBe(3) }) test('兩個數相減', () => { expect(sub(5, 2)).toBe(3) }) }) describe('測試四則運算2', () => { beforeAll(() => { console.log('beforeAll in 測試四則運算2') }) beforeEach(() => { console.log('beforeEach in 測試四則運算2') }) test('兩個數相加', () => { expect(add(3, 4)).toBe(7) }) test('兩個數相減', () => { expect(sub(11, 4)).toBe(7) }) }) ``` + 測試輸出 ``` stdout | src/utils/__tests__/func.spec.ts beforeAll stdout | src/utils/__tests__/func.spec.ts > 測試四則運算1 beforeAll in 測試四則運算1 stdout | src/utils/__tests__/func.spec.ts > 測試四則運算1 > 兩個數相加 beforeEach beforeEach in 測試四則運算1 stdout | src/utils/__tests__/func.spec.ts > 測試四則運算1 > 兩個數相減 beforeEach beforeEach in 測試四則運算1 stdout | src/utils/__tests__/func.spec.ts > 測試四則運算2 beforeAll in 測試四則運算2 stdout | src/utils/__tests__/func.spec.ts > 測試四則運算2 > 兩個數相加 beforeEach beforeEach in 測試四則運算2 stdout | src/utils/__tests__/func.spec.ts > 測試四則運算2 > 兩個數相減 beforeEach beforeEach in 測試四則運算2 ✓ src/utils/__tests__/func.spec.ts (4) ✓ 測試四則運算1 (2) ✓ 兩個數相加 ✓ 兩個數相減 ✓ 測試四則運算2 (2) ✓ 兩個數相加 ✓ 兩個數相減 Test Files 1 passed (1) Tests 4 passed (4) Start at 17:47:01 Duration 35ms ``` ### 捏造 API 回傳資料 `vi` |🚨 <span class="caution">CAUTION</span>| |:---| |切記 `vi.spyOn` 後才能 `mount`,否則會失效 | |📗 <span class="tip">TIP</span>:`vi.spyOn` | |:---| | 第一個參數:一個物件 | | 第二個參數:一個字串,而且字串必須是物件的方法 | + 範例 + 測試案例 ```ts import { test, describe, expect, vi } from 'vitest' import { mount } from '@vue/test-utils' import App from '../src/App.vue' import axios from 'axios' describe('App.vue', () => { // 監聽 vi.spyOn const getProducts = vi.spyOn(axios, 'get') // 捏造假資料 vi.mockReturnValue getProducts.mockReturnValue({ data: [ { "id": 1, "title": "Fjallraven", "price": 109.95, "description": "Your perfect pack for everyday use", "category": "men's clothing", "image": "https://fakestoreapi.com/img/81fPKd.jpg", "rating": { "rate": 3.9, "count": 120 } }, // ... ]}) // 掛載 mount const wrapper = mount(App) // ... }) ``` ## Test Component [@vue/test-utils][vue: test-utils] 是 Vue 官方提供的測試工具,主要是讓你可以測試 Vue 的元件。\ 如果你在 `pnpm create vue` 時就有選擇 Vitest,那它會自動幫你列入 dependency。 ### 掛載 `mount` + 方法 + `wrapper.text()`:取得元件內的文字 + `wrapper.html()`:取得元件內的 HTML + `wrapper.find()`:找到元件內的某個元素 + `wrapper.findAll()`:找到元件內的所有元素 + `wrapper.vm`:取得元件內的資料 (例如:data) + 待測功能 (Vue component) ![HelloWorld.vue](https://hackmd.io/_uploads/BkSNeBhqJx.png) ```html <script setup lang="ts"> defineProps<{ msg: string }>() </script> <template> <div class="greetings"> <h1 class="green"> {{ msg }} </h1> <h3> You’ve successfully created a project with <a href="https://vite.dev/" target="_blank" rel="noopener">Vite</a> + <a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>. What's next? </h3> </div> </template> <style scoped> h1 { font-weight: 500; font-size: 2.6rem; position: relative; top: -10px; } h3 { font-size: 1.2rem; } .greetings h1, .greetings h3 { text-align: center; } @media (min-width: 1024px) { .greetings h1, .greetings h3 { text-align: left; } } </style> ``` + 測試 ```ts import { mount } from '@vue/test-utils' import { describe, expect, it } from 'vitest' import HelloWorld from '../HelloWorld.vue' describe('helloWorld', () => { it('renders properly', () => { const wrapper = mount(HelloWorld, { props: { msg: 'Hello Vitest' } }) expect(wrapper.text()).toContain('Hello Vitest') }) }) ``` ## Coverage Vanilla JS 原本就有檢測 coverage 的第三方套件,分別是 [v8] 和 [istanbul]。\ 而 Vitest 兩者皆支援,分別為 [@vitest/coverage-v8][vitest: coverage] 和 [@vitest/coverage-istanbul][vitest: coverage]。\ 需額外下載。 ### reference + 🔗 [**ShawnL - 【進階ノ章】覆蓋率(Coverage)**](https://ithelp.ithome.com.tw/articles/10309187) + 🔗 [**Vitest - coverage**][vitest: coverage] ### install Choose one ```bash npm i -D @vitest/coverage-v8 ``` ```bash npm i -D @vitest/coverage-istanbul ``` [vue: test-utils]: https://test-utils.vuejs.org/api/ [vitest: expect]: https://vitest.dev/api/expect [vitest: coverage]: https://vitest.dev/guide/coverage [v8]: https://v8.dev/blog/javascript-code-coverage [istanbul]: https://istanbul.js.org/