Category: guide
Vitest — Unit Testing สำหรับ TypeScript และ Vite Projects
Vitest คือ test runner ที่เร็วที่สุดสำหรับ Vite-based projects — setup วิธีเขียน test และ pattern ที่ใช้จริง
สารบัญ
ทำไม Vitest
Vitest ใช้ Vite เป็น engine จึงเร็วมาก รองรับ TypeScript ในตัว, ESM native, และใช้ Jest-compatible API ทำให้ย้ายโค้ดทดสอบเดิมได้โดยแทบไม่แก้ไข
npm install -D vitest
Setup
// package.json
{
"scripts": {
"test": "vitest",
"test:ui": "vitest --ui",
"test:run":"vitest run"
}
}
// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
environment: 'node', // หรือ 'jsdom' สำหรับ DOM testing
globals: true, // ใช้ describe/it/expect โดยไม่ต้อง import
},
});
เขียน Test แรก
// src/utils/math.ts
export function add(a: number, b: number): number {
return a + b;
}
// src/utils/math.test.ts
import { describe, it, expect } from 'vitest';
import { add } from './math';
describe('add()', () => {
it('บวกตัวเลขสองตัวถูกต้อง', () => {
expect(add(2, 3)).toBe(5);
});
it('บวกค่าลบได้', () => {
expect(add(-1, 1)).toBe(0);
});
});
Matchers ที่ใช้บ่อย
expect(value).toBe(5) // strict equal (===)
expect(value).toEqual({ a: 1 }) // deep equal
expect(value).toBeTruthy() // truthy
expect(value).toBeNull() // null
expect(arr).toContain('item') // array มี item
expect(str).toMatch(/pattern/) // regex match
expect(fn).toThrow('error message') // throw error
expect(fn).not.toThrow() // ไม่ throw
Async Testing
import { it, expect } from 'vitest';
it('fetch ข้อมูลสำเร็จ', async () => {
const data = await fetchUser(1);
expect(data.id).toBe(1);
expect(data.name).toBeTruthy();
});
Mock Functions
import { vi, it, expect } from 'vitest';
it('เรียก callback เมื่อ submit', () => {
const onSubmit = vi.fn();
handleSubmit('data', onSubmit);
expect(onSubmit).toHaveBeenCalledOnce();
expect(onSubmit).toHaveBeenCalledWith('data');
});
Mock Module
vi.mock('./api', () => ({
fetchPosts: vi.fn().mockResolvedValue([
{ id: 1, title: 'Test Post' },
]),
}));
DOM Testing กับ jsdom
// vitest.config.ts
test: { environment: 'jsdom' }
import { it, expect, beforeEach } from 'vitest';
beforeEach(() => {
document.body.innerHTML = '<button id="btn">Click</button>';
});
it('button มี text ถูกต้อง', () => {
const btn = document.getElementById('btn');
expect(btn?.textContent).toBe('Click');
});
Test Coverage
npm install -D @vitest/coverage-v8
# vitest.config.ts
test: {
coverage: {
provider: 'v8',
reporter: ['text', 'html'],
include: ['src/**/*.ts'],
exclude: ['src/**/*.test.ts'],
},
}
vitest run --coverage
Vitest UI
npm install -D @vitest/ui
npx vitest --ui
เปิด browser ที่ http://localhost:51204/__vitest__/ จะเห็น test results แบบ visual พร้อม re-run และ filter
Pattern: test ตาม behavior ไม่ใช่ implementation
// ❌ test implementation
it('เรียก calculateTotal ครั้งเดียว', () => {
expect(spy).toHaveBeenCalledOnce();
});
// ✓ test behavior
it('แสดงราคารวมที่ถูกต้อง', () => {
expect(screen.getByText('฿250')).toBeTruthy();
});