เขียน Unit Test ด้วย React + TypeScript + Vitest

ปกติโปรเจ็ค React ส่วนใหญ่จะนิยมใช้ Jest + Testing Library กันใช่มั้ย แต่ช่วงนี้ผมได้ลองใช้ Vitest แล้วรู้สึกชอบมากกว่า ก็เลยพยายามเปลี่ยนมาใช้ Vitest (เอามาแทนที่ Jest) วันนี้เลยมาเขียนบล็อกแนะนำการ ติดตั้ง การตั้งค่า Vitest สำหรับโปรเจ็ค React.js กันครับ
โดยตัวอย่างโปรเจ็ค ผมจะใช้เป็น default template นะครับ
- React + TypeScript ด้วยการใช้ Vite
- Vitest


สร้างโปรเจ็ค
เริ่มต้น ผมสร้าง default template โดยการใช้ Vite (จริงๆ Vitest ใช้ได้เกือบทุก library/framework นะครับ)
npm create vite@latest hello-vitest -- --template react-ts
เราก็จะได้ Project React + Vite มาแบบง่ายๆ 1 เว็บ
ติดตั้ง Vitest
การติดตั้ง Vitest คือ
npm install -D vitest
การ Config Vitest
โดยปกติ ถ้าโปรเจ็คเราเป็น Vite ตัว Vitest จะอ่าน config จาก vite.config.ts
ได้เลย
import { defineConfig } from 'vite'import react from '@vitejs/plugin-react-swc'
// https://vitejs.dev/config/export default defineConfig({ plugins: [react()], test: { globals: true }})
แต่ว่าการ config vitest ในไฟล์ config ของ vite นั้น ตัว TypeScript มันไม่รู้จัก เราต้องบอกให้มัน reference ไปที่ Vitest Type ครับ คือเพิ่ม reference type ที่ส่วน top (ใช้ triple slash) จะได้เป็นแบบนี้
/// <reference types="vitest" />import { defineConfig } from 'vite'import react from '@vitejs/plugin-react-swc'
// https://vitejs.dev/config/export default defineConfig({ plugins: [react()], test: { globals: true }})
หรืออีกตัวเลือกคือ ไม่ใช้ triple slash แต่เราก็เปลี่ยน defineConfig จาก vitest/config แทนที่ vite แบบนี้
import { defineConfig } from 'vite'import { defineConfig } from 'vitest/config'
ส่วนที่ผมกำหนด globals: true คือ ทำให้เราสามารถใช้ describe, it test ได้แบบ global เหมือนกับ Jest (ข้อดีคือเหมาะสำหรับคนที่ migrate จาก Jest ไม่ต้องแก้อะไรมาก)
แต่ถ้าใช้ global เป็น false (default) เวลาเขียนเทส ก็จะ import จาก vitest ไม่ใช่ global แล้ว แบบนี้
import { describe, it, expect } from 'vitest'
describe('something', () => { it('should work'), () => { expect(true).toEqual(true) })})
ทำการเพิ่ม Compiler Options ที่ไฟล์ tsconfig.json เพื่อให้ TypeScript รู้จัก Vitest globals (ไม่งั้นตัว VS Code อาจจะฟ้อง error)
{ "compilerOptions": { "types": ["vitest/globals"] }}
การ Config ด้วย vitest.config.ts
นอกจากการ Config ผ่าน Vite Config แล้ว เราสามารถ config ด้วย Vitest เอง คือไฟล์ vitest.config.ts การ config แบบนี้ เหมาะสำหรับโปรเจ็คที่เราไม่ได้ใช้ Vite นั่นเอง
- ตัว vitest.config.ts จะเป็นมี priority สูงกว่า คือถ้ามีไฟล์ vitest กับ vite ตัว config จะอ่าน vitest.config.ts ก่อน อ่านไฟล์ vite.config.ts
- ตัวอย่าง config (เหมือนกับ vite.config.ts)
import { defineConfig } from 'vitest/config'
export default defineConfig({ test: { // ... }})
เพิ่ม script test ใน package.json
{ "scripts": { "test": "vitest" }}
เขียนเทสแรก
ลองเขียน test ขึ้นมาไฟล์นึง สมมติ ผมจะทำ helpers function ซักตัว เกี่ยวกับการ calculate ละกัน ผมตั้งชื่อว่า calculate.spec.ts ในโฟลเดอร์ src/helpers
import { add } from './calculate'
describe('add', () => { it('should add two numbers', () => { expect(add(1, 2)).toBe(3) })})
และไฟล์ calculate.ts
แบบนี้
export const add = (a: number, b: number): number => a + b
ปกติ default ถ้าเราไม่ได้ config อะไร ตัว vitest จะ scan หาไฟล์ _.spec หรือ _.test และรันเทสให้เราครับ
ลองรันเทสดู
npm test
จะได้ผลลัพธ์ประมาณนี้
✓ src/helpers/calculate.spec.ts (1)
Test Files 1 passed (1)Tests 1 passed (1)Start at 15:40:14Duration 11ms
ตัว vitest จะเข้าโหมด watch ให้เราเอง เวลาเราแก้ไขไฟล์ ตัว test ก็จะทำการ re-run ใหม่ครับ สะดวกมาก (กด q เพื่อออกจากโหมดเทส)
Test Coverage
ตัว default coverage ของ Vitest คือ v8 ถ้าเราไม่ได้ config ว่าจะให้เป็น reporter provider อะไร แต่ถ้าเราจะใช้ coverage ยังไงก็ต้องติดตั้ง package ก่อน
npm install -D @vitest/coverage-v8
(กรณีไม่ได้ติดตั้ง แล้วเราสั่งรันเทสด้วย coverage ตัว Vitest จะ detect และถามเราว่า ต้องการติดตั้ง coverage-v8 หรือไม่ ก็เลือกติดตั้งวิธีนี้ก็ได้เช่นกัน)
ทีนี้เวลาเราจะเทสด้วย coverage ก็ใส่ option ลงไป แบบนี้
vitest --coverage
Vitest UI
ติดตั้ง Vitest UI เพื่อให้เราสามารถรันโหมด UI ได้ ข้อดีคือ ดูง่าย สามารถดูเทส รันเทส ผ่าน Browser ได้เลย
npm install -D @vitest/ui
จากนั้นเพิ่ม script สำหรับ ui ต่อจาก coverage แบบนี้
{ "scripts": { "test": "vitest", "test:coverage": "vitest --coverage", "test:ui": "vitest --ui" }}
ผลลัพธ์เมื่อรัน coverage
Test Files 1 passed (1)Tests 1 passed (1)Start at 14:08:25Duration 189ms (transform 21ms, setup 0ms, collect 9ms, tests 2ms, environment 0ms, prepare 58ms)
% Coverage report from v8--------------|---------|----------|---------|---------|-------------------File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s--------------|---------|----------|---------|---------|-------------------All files | 100 | 100 | 100 | 100 | calculate.ts | 100 | 100 | 100 | 100 |--------------|---------|----------|---------|---------|-------------------
ผลลัพธ์เมื่อรัน vitest ui
สรุป
บทความนี้ก็เป็นวิธีการตั้งค่า Vitest แบบง่ายๆนะครับ ลองทดลองเล่นกันดู เขียนเทสเพิ่ม ลองรัน รวมถึง ตัว environment default คือ node.js เราสามารถเทสด้วย jsdom หรือ happy-dom ได้ เพื่อให้ environment เป็น web ครับ ลองดูนะครับ เดี๋ยวบทความหน้าจะเขียนเรื่องการเทสตัว React Component กันครับ
Happy Coding ❤️
Reference

- Authors
-
Chai Phonbopit
เป็น Web Dev ในบริษัทแห่งหนึ่ง ทำงานมา 10 ปีกว่าๆ ด้วยภาษาและเทคโนโลยี เช่น JavaScript, Node.js, React, Vue และปัจจุบันกำลังสนใจในเรื่องของ Blockchain และ Crypto กำลังหัดเรียนภาษา Rust