Astro Testing: Unit Test และ E2E Test
#astro13 เม.ย. 2569
Astro Testing: Unit Test และ E2E Test
การทดสอบเป็นส่วนสำคัญของการพัฒนา software ที่มีคุณภาพ Astro รองรับการทดสอบหลายรูปแบบตั้งแต่ unit tests ไปจนถึง end-to-end tests บทความนี้จะแนะนำการตั้งค่าและเขียน tests สำหรับ Astro projects
ประเภทของ Tests
- Unit Tests: ทดสอบ functions และ utilities แยกส่วน
- Component Tests: ทดสอบ UI components
- Integration Tests: ทดสอบการทำงานร่วมกันของหลาย components
- E2E Tests: ทดสอบ user flows ทั้งหมด
Unit Testing ด้วย Vitest
ติดตั้ง Vitest:
npm install -D vitest @vitest/ui
แก้ไข package.json:
{
"scripts": {
"test": "vitest",
"test:run": "vitest run",
"test:ui": "vitest --ui",
"test:coverage": "vitest run --coverage"
}
}
สร้าง vitest.config.ts:
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'node',
include: ['src/**/*.{test,spec}.{ts,tsx}'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
include: ['src/**/*.ts'],
exclude: ['src/**/*.test.ts', 'src/env.d.ts'],
},
},
});
เขียน Unit Tests
// src/utils/formatDate.ts
export function formatDate(date: Date | string, locale = 'th-TH'): string {
const d = typeof date === 'string' ? new Date(date) : date;
return d.toLocaleDateString(locale, {
year: 'numeric',
month: 'long',
day: 'numeric',
});
}
export function formatRelativeTime(date: Date | string): string {
const d = typeof date === 'string' ? new Date(date) : date;
const now = new Date();
const diffMs = now.getTime() - d.getTime();
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
if (diffDays === 0) return 'วันนี้';
if (diffDays === 1) return 'เมื่อวาน';
if (diffDays < 7) return `${diffDays} วันที่แล้ว`;
if (diffDays < 30) return `${Math.floor(diffDays / 7)} สัปดาห์ที่แล้ว`;
return formatDate(d);
}
// src/utils/formatDate.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { formatDate, formatRelativeTime } from './formatDate';
describe('formatDate', () => {
it('formats date in Thai locale', () => {
const date = new Date('2024-01-15');
const result = formatDate(date);
expect(result).toContain('2567'); // Thai year
});
it('accepts string date', () => {
const result = formatDate('2024-06-01');
expect(typeof result).toBe('string');
expect(result.length).toBeGreaterThan(0);
});
it('formats with custom locale', () => {
const date = new Date('2024-01-15');
const result = formatDate(date, 'en-US');
expect(result).toContain('2024');
});
});
describe('formatRelativeTime', () => {
beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime(new Date('2024-06-15'));
});
afterEach(() => {
vi.useRealTimers();
});
it('returns วันนี้ for today', () => {
expect(formatRelativeTime(new Date('2024-06-15'))).toBe('วันนี้');
});
it('returns เมื่อวาน for yesterday', () => {
expect(formatRelativeTime(new Date('2024-06-14'))).toBe('เมื่อวาน');
});
it('returns days ago for recent dates', () => {
expect(formatRelativeTime(new Date('2024-06-12'))).toBe('3 วันที่แล้ว');
});
});
Testing API Routes
// src/pages/api/posts.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
// Mock database
vi.mock('../../lib/db', () => ({
getPosts: vi.fn(),
createPost: vi.fn(),
}));
import { getPosts, createPost } from '../../lib/db';
describe('GET /api/posts', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('returns posts with pagination', async () => {
const mockPosts = [
{ id: '1', title: 'Post 1', slug: 'post-1' },
{ id: '2', title: 'Post 2', slug: 'post-2' },
];
vi.mocked(getPosts).mockResolvedValue(mockPosts as any);
const request = new Request('http://localhost/api/posts?page=1');
// Test your API handler
expect(getPosts).toBeDefined();
});
});
Component Testing ด้วย @testing-library
npm install -D @testing-library/dom @testing-library/user-event jsdom
อัปเดต vitest.config.ts:
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['./src/test/setup.ts'],
},
});
// src/test/setup.ts
import '@testing-library/jest-dom';
E2E Testing ด้วย Playwright
ติดตั้ง Playwright:
npm install -D @playwright/test
npx playwright install
สร้าง playwright.config.ts:
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
baseURL: 'http://localhost:4321',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'Mobile Chrome', use: { ...devices['Pixel 5'] } },
],
webServer: {
command: 'npm run dev',
url: 'http://localhost:4321',
reuseExistingServer: !process.env.CI,
},
});
เขียน E2E tests:
// e2e/blog.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Blog', () => {
test('shows list of posts', async ({ page }) => {
await page.goto('/blog');
await expect(page.getByRole('heading', { level: 1 })).toBeVisible();
await expect(page.locator('article')).toHaveCount(9);
});
test('navigates to post detail', async ({ page }) => {
await page.goto('/blog');
const firstPost = page.locator('article').first();
const title = await firstPost.locator('h2').textContent();
await firstPost.locator('a').click();
await expect(page.locator('h1')).toHaveText(title!);
});
test('search works', async ({ page }) => {
await page.goto('/blog');
await page.getByPlaceholder('ค้นหา').fill('Astro');
await page.waitForResponse('/api/search*');
const results = page.locator('.search-results a');
await expect(results.first()).toBeVisible();
});
});
test.describe('Contact Form', () => {
test('submits successfully', async ({ page }) => {
await page.goto('/contact');
await page.getByLabel('ชื่อ').fill('ทดสอบ');
await page.getByLabel('อีเมล').fill('test@example.com');
await page.getByLabel('ข้อความ').fill('ข้อความทดสอบ');
await page.getByRole('button', { name: 'ส่ง' }).click();
await expect(page.getByText('ส่งสำเร็จ')).toBeVisible();
});
});
สรุป
การทดสอบ Astro projects ด้วย Vitest สำหรับ unit tests และ Playwright สำหรับ E2E tests ทำให้มั่นใจได้ว่า application ทำงานถูกต้อง การเขียน tests ที่ดีช่วยลด bugs, เพิ่มความมั่นใจในการ refactor, และทำให้ codebase มีคุณภาพสูงขึ้น