- Component Testing (React) ด้วย Vitest
- 3. สร้าง React Project
- 4. ติดตั้ง Testing Dependencies
- 5. ตั้งค่า Vitest
- 6. ตั้งค่า jest-dom
- 7. เพิ่ม Test Script
- 8. Component Test ตัวแรก
- 9. การใช้ screen
- 10. ทดสอบ Component ที่มี Props
- 11. ทดสอบ State ด้วย Counter
- 12. ทดสอบ Callback ด้วย vi.fn()
- 13. ทดสอบ Form Input
- 14. ทดสอบ Form Submit
- 15. ทดสอบ Validation
- 16. getBy, queryBy และ findBy ต่างกันอย่างไร
- 17. ทดสอบ Async Component
- 18. Mock Module ด้วย vi.mock()
- 19. ทดสอบ Conditional Rendering
- 20. ทดสอบ Component ที่ใช้ Context
- 21. สร้าง Custom Render
- 22. Code Coverage
- 23. ตัวอย่างโครงสร้าง Project
- 24. แนวทางการตั้งชื่อ Test
- 25. แนวทางเลือก Query
- 26. userEvent กับ fireEvent
- 27. สิ่งที่ไม่ควรทดสอบมากเกินไป
- 28. Component Test vs Unit Test vs E2E Test
- 29. Testing Workflow ที่แนะนำ
- 30. Best Practices
- 31. Checklist สำหรับ React Component Testing
- สรุป
#Component Testing (React) ด้วย Vitest
การทดสอบ React Component ช่วยตรวจสอบว่า UI แสดงผลและตอบสนองต่อผู้ใช้ได้ถูกต้อง เช่น
- Component แสดงข้อความตาม
propsหรือไม่ - เมื่อกดปุ่มแล้ว state เปลี่ยนหรือไม่
- Form รับข้อมูลและแสดง validation ถูกต้องหรือไม่
- Callback ถูกเรียกด้วยข้อมูลที่ถูกต้องหรือไม่
- UI แสดง Loading / Success / Error ตามสถานะของระบบหรือไม่
เครื่องมือที่นิยมใช้ร่วมกันคือ
- Vitest — Test runner ที่ทำงานร่วมกับ Vite ได้ดี
- React Testing Library — Render และตรวจสอบ React Component จากมุมมองผู้ใช้
- user-event — จำลองพฤติกรรมผู้ใช้ เช่น click, type, tab
- jest-dom — เพิ่ม matcher สำหรับ DOM เช่น
toBeInTheDocument() - jsdom — จำลอง Browser DOM ใน Node.js
แนวคิดสำคัญคือ ทดสอบพฤติกรรมที่ผู้ใช้มองเห็นและใช้งานได้ มากกว่าการทดสอบรายละเอียด implementation ภายใน Component
#1. Component Testing คืออะไร
Component Testing คือการนำ UI Component มาทดสอบแบบแยกส่วนหรือร่วมกับ dependency ที่จำเป็น เพื่อยืนยันว่า Component ทำงานตาม expected behavior
ตัวอย่าง Component:
type GreetingProps = {
name: string
}
export function Greeting({ name }: GreetingProps) {
return <h1>Hello, {name}</h1>
}
สิ่งที่ควรทดสอบคือ
เมื่อส่ง name="Naruapon"
ผู้ใช้ควรเห็นข้อความ "Hello, Naruapon"
ไม่จำเป็นต้องตรวจสอบ state ภายในหรือโครงสร้าง implementation ที่ผู้ใช้ไม่ได้รับรู้
#2. Vitest เหมาะกับ React อย่างไร
Vitest เป็น Test Framework ที่ออกแบบให้ทำงานใน ecosystem ของ Vite จึงเหมาะกับ React + Vite โดยเฉพาะ
ข้อดีหลัก ได้แก่
- ใช้ configuration และ module resolution ใกล้เคียงกับ Vite
- รองรับ TypeScript และ JSX/TSX
- รองรับ Watch Mode
- มี API เช่น
describe,it,expect,vi.fn,vi.mock - รองรับ Snapshot
- รองรับ Code Coverage ผ่าน V8 หรือ Istanbul
- รองรับทั้ง
jsdom,happy-domและ Browser Mode
#3. สร้าง React Project
ตัวอย่างใช้ Vite + React + TypeScript
npm create vite@latest react-vitest-demo
เลือก
Framework: React
Variant: TypeScript
จากนั้น
cd react-vitest-demo
npm install
#4. ติดตั้ง Testing Dependencies
npm install -D vitest \
@testing-library/react \
@testing-library/dom \
@testing-library/jest-dom \
@testing-library/user-event \
jsdom
ถ้าต้องการ Code Coverage
npm install -D @vitest/coverage-v8
#5. ตั้งค่า Vitest
สร้างไฟล์
vitest.config.ts
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
globals: true,
setupFiles: './src/test/setup.ts',
},
})
เหตุผลที่ใช้
environment: 'jsdom'
เพราะ React Component ต้องใช้งาน DOM เช่น
document
window
HTMLElement
button
input
ซึ่งไม่มีอยู่ใน Node.js environment ปกติ
#6. ตั้งค่า jest-dom
สร้างไฟล์
src/test/setup.ts
import '@testing-library/jest-dom/vitest'
ทำให้สามารถเขียน assertion เช่น
expect(element).toBeInTheDocument()
expect(button).toBeDisabled()
expect(input).toHaveValue('hello')
expect(element).toBeVisible()
ได้โดยตรง
#7. เพิ่ม Test Script
แก้ไข package.json
{
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"test": "vitest",
"test:run": "vitest run",
"test:coverage": "vitest run --coverage"
}
}
รันแบบ Watch Mode
npm test
รันหนึ่งครั้ง
npm run test:run
#8. Component Test ตัวแรก
สร้าง
src/components/Greeting.tsx
type GreetingProps = {
name: string
}
export function Greeting({ name }: GreetingProps) {
return <h1>Hello, {name}</h1>
}
สร้าง test
src/components/Greeting.test.tsx
import { render, screen } from '@testing-library/react'
import { describe, expect, it } from 'vitest'
import { Greeting } from './Greeting'
describe('Greeting', () => {
it('shows greeting with name', () => {
render(<Greeting name="Naruapon" />)
expect(
screen.getByRole('heading', {
name: 'Hello, Naruapon',
})
).toBeInTheDocument()
})
})
โครงสร้างของ test คือ
Arrange → เตรียมข้อมูล
Act → กระทำกับ Component
Assert → ตรวจสอบผลลัพธ์
ตัวอย่างนี้ไม่มี Act เพราะการ render Component เพียงอย่างเดียวก็เพียงพอ
#9. การใช้ screen
React Testing Library มี screen สำหรับค้นหา element ใน DOM
ตัวอย่าง
screen.getByRole('button')
screen.getByText('Save')
screen.getByLabelText('Email')
screen.getByPlaceholderText('Search')
แนวทางที่แนะนำคือใช้ query ที่ใกล้เคียงกับสิ่งที่ผู้ใช้และ assistive technology รับรู้
ตัวอย่างที่ดี
screen.getByRole('button', { name: 'Save' })
แทน
container.querySelector('.save-button')
เพราะ test จะผูกกับ behavior มากกว่า CSS implementation
#10. ทดสอบ Component ที่มี Props
type UserCardProps = {
name: string
role: string
}
export function UserCard({ name, role }: UserCardProps) {
return (
<article>
<h2>{name}</h2>
<p>{role}</p>
</article>
)
}
Test:
import { render, screen } from '@testing-library/react'
import { describe, expect, it } from 'vitest'
import { UserCard } from './UserCard'
describe('UserCard', () => {
it('renders user information', () => {
render(
<UserCard
name="Alice"
role="Software Engineer"
/>
)
expect(
screen.getByRole('heading', { name: 'Alice' })
).toBeInTheDocument()
expect(
screen.getByText('Software Engineer')
).toBeInTheDocument()
})
})
#11. ทดสอบ State ด้วย Counter
Component:
import { useState } from 'react'
export function Counter() {
const [count, setCount] = useState(0)
return (
<div>
<p>Count: {count}</p>
<button
onClick={() => setCount((value) => value + 1)}
>
Increment
</button>
</div>
)
}
Test:
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { describe, expect, it } from 'vitest'
import { Counter } from './Counter'
describe('Counter', () => {
it('increments count when button is clicked', async () => {
const user = userEvent.setup()
render(<Counter />)
expect(
screen.getByText('Count: 0')
).toBeInTheDocument()
await user.click(
screen.getByRole('button', {
name: 'Increment',
})
)
expect(
screen.getByText('Count: 1')
).toBeInTheDocument()
})
})
สังเกตว่าเราไม่ได้ตรวจสอบ useState() โดยตรง
สิ่งที่ทดสอบคือ behavior:
Click Increment
↓
UI เปลี่ยนจาก Count: 0
↓
เป็น Count: 1
#12. ทดสอบ Callback ด้วย vi.fn()
Component:
type SaveButtonProps = {
onSave: () => void
}
export function SaveButton({
onSave,
}: SaveButtonProps) {
return (
<button onClick={onSave}>
Save
</button>
)
}
Test:
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { describe, expect, it, vi } from 'vitest'
import { SaveButton } from './SaveButton'
describe('SaveButton', () => {
it('calls onSave when clicked', async () => {
const user = userEvent.setup()
const onSave = vi.fn()
render(<SaveButton onSave={onSave} />)
await user.click(
screen.getByRole('button', {
name: 'Save',
})
)
expect(onSave).toHaveBeenCalledTimes(1)
})
})
vi.fn() สร้าง Mock Function ที่สามารถตรวจสอบได้ว่า
expect(fn).toHaveBeenCalled()
expect(fn).toHaveBeenCalledTimes(1)
expect(fn).toHaveBeenCalledWith(data)
#13. ทดสอบ Form Input
Component:
import { useState } from 'react'
export function LoginForm() {
const [email, setEmail] = useState('')
return (
<form>
<label htmlFor="email">
Email
</label>
<input
id="email"
type="email"
value={email}
onChange={(event) =>
setEmail(event.target.value)
}
/>
<p>Email: {email}</p>
</form>
)
}
Test:
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { describe, expect, it } from 'vitest'
import { LoginForm } from './LoginForm'
describe('LoginForm', () => {
it('updates email when user types', async () => {
const user = userEvent.setup()
render(<LoginForm />)
const input = screen.getByRole('textbox', {
name: 'Email',
})
await user.type(
input,
'user@example.com'
)
expect(input).toHaveValue(
'user@example.com'
)
expect(
screen.getByText(
'Email: user@example.com'
)
).toBeInTheDocument()
})
})
#14. ทดสอบ Form Submit
Component:
import { FormEvent, useState } from 'react'
type LoginFormProps = {
onSubmit: (email: string) => void
}
export function LoginForm({
onSubmit,
}: LoginFormProps) {
const [email, setEmail] = useState('')
function handleSubmit(
event: FormEvent<HTMLFormElement>
) {
event.preventDefault()
onSubmit(email)
}
return (
<form onSubmit={handleSubmit}>
<label htmlFor="email">
Email
</label>
<input
id="email"
value={email}
onChange={(event) =>
setEmail(event.target.value)
}
/>
<button type="submit">
Login
</button>
</form>
)
}
Test:
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { describe, expect, it, vi } from 'vitest'
import { LoginForm } from './LoginForm'
describe('LoginForm', () => {
it('submits entered email', async () => {
const user = userEvent.setup()
const onSubmit = vi.fn()
render(
<LoginForm onSubmit={onSubmit} />
)
await user.type(
screen.getByRole('textbox', {
name: 'Email',
}),
'user@example.com'
)
await user.click(
screen.getByRole('button', {
name: 'Login',
})
)
expect(onSubmit).toHaveBeenCalledWith(
'user@example.com'
)
})
})
#15. ทดสอบ Validation
Component:
import { useState } from 'react'
export function RegisterForm() {
const [email, setEmail] = useState('')
const [error, setError] = useState('')
function validate() {
if (!email.includes('@')) {
setError('Invalid email')
return
}
setError('')
}
return (
<div>
<label htmlFor="email">
Email
</label>
<input
id="email"
value={email}
onChange={(event) =>
setEmail(event.target.value)
}
/>
<button onClick={validate}>
Validate
</button>
{error && (
<p role="alert">{error}</p>
)}
</div>
)
}
Test:
it('shows error for invalid email', async () => {
const user = userEvent.setup()
render(<RegisterForm />)
await user.type(
screen.getByRole('textbox', {
name: 'Email',
}),
'invalid-email'
)
await user.click(
screen.getByRole('button', {
name: 'Validate',
})
)
expect(
screen.getByRole('alert')
).toHaveTextContent('Invalid email')
})
#16. getBy, queryBy และ findBy ต่างกันอย่างไร
#getBy
ใช้เมื่อ element ควรมีอยู่ทันที
screen.getByRole('button')
ถ้าไม่พบจะ throw error
#queryBy
ใช้เมื่อต้องการตรวจสอบว่า element ไม่มีอยู่
expect(
screen.queryByText('Error')
).not.toBeInTheDocument()
#findBy
ใช้กับ UI แบบ asynchronous
const message =
await screen.findByText('Success')
เหมาะกับกรณี
API Request
Loading
setTimeout
Promise
State update แบบ async
#17. ทดสอบ Async Component
ตัวอย่าง Component:
import { useEffect, useState } from 'react'
type User = {
name: string
}
type UserProfileProps = {
loadUser: () => Promise<User>
}
export function UserProfile({
loadUser,
}: UserProfileProps) {
const [user, setUser] =
useState<User | null>(null)
useEffect(() => {
loadUser().then(setUser)
}, [loadUser])
if (!user) {
return <p>Loading...</p>
}
return <h1>{user.name}</h1>
}
Test:
import { render, screen } from '@testing-library/react'
import { describe, expect, it, vi } from 'vitest'
import { UserProfile } from './UserProfile'
describe('UserProfile', () => {
it('shows user after loading', async () => {
const loadUser = vi.fn()
.mockResolvedValue({
name: 'Alice',
})
render(
<UserProfile loadUser={loadUser} />
)
expect(
screen.getByText('Loading...')
).toBeInTheDocument()
expect(
await screen.findByRole(
'heading',
{ name: 'Alice' }
)
).toBeInTheDocument()
})
})
#18. Mock Module ด้วย vi.mock()
ตัวอย่าง service
// userService.ts
export async function getUser() {
const response =
await fetch('/api/user')
return response.json()
}
Component
import { useEffect, useState } from 'react'
import { getUser } from './userService'
export function Profile() {
const [name, setName] = useState('')
useEffect(() => {
getUser().then((user) => {
setName(user.name)
})
}, [])
return (
<div>
{name || 'Loading...'}
</div>
)
}
Test:
import { render, screen } from '@testing-library/react'
import { describe, expect, it, vi } from 'vitest'
import { Profile } from './Profile'
vi.mock('./userService', () => ({
getUser: vi.fn().mockResolvedValue({
name: 'Alice',
}),
}))
describe('Profile', () => {
it('shows loaded user', async () => {
render(<Profile />)
expect(
await screen.findByText('Alice')
).toBeInTheDocument()
})
})
สำหรับระบบขนาดใหญ่ที่ต้อง mock HTTP request หลาย endpoint ควรพิจารณาใช้ Mock Service Worker (MSW) เพื่อจำลอง network layer ให้ใกล้เคียงระบบจริงมากกว่า mock
fetchในแต่ละ test
#19. ทดสอบ Conditional Rendering
Component:
type StatusProps = {
loading: boolean
error?: string
}
export function Status({
loading,
error,
}: StatusProps) {
if (loading) {
return <p>Loading...</p>
}
if (error) {
return (
<p role="alert">
{error}
</p>
)
}
return <p>Ready</p>
}
Tests:
describe('Status', () => {
it('shows loading state', () => {
render(
<Status loading={true} />
)
expect(
screen.getByText('Loading...')
).toBeInTheDocument()
})
it('shows error state', () => {
render(
<Status
loading={false}
error="Server Error"
/>
)
expect(
screen.getByRole('alert')
).toHaveTextContent(
'Server Error'
)
})
it('shows ready state', () => {
render(
<Status loading={false} />
)
expect(
screen.getByText('Ready')
).toBeInTheDocument()
})
})
#20. ทดสอบ Component ที่ใช้ Context
ตัวอย่าง Context:
import {
createContext,
useContext,
} from 'react'
const ThemeContext =
createContext('light')
export function ThemeName() {
const theme =
useContext(ThemeContext)
return <p>Theme: {theme}</p>
}
export { ThemeContext }
Test:
render(
<ThemeContext.Provider
value="dark"
>
<ThemeName />
</ThemeContext.Provider>
)
expect(
screen.getByText('Theme: dark')
).toBeInTheDocument()
#21. สร้าง Custom Render
ถ้าหลาย Component ต้องใช้ Provider เดิมซ้ำ ๆ เช่น
React Router
Redux
Zustand
TanStack Query
Theme Provider
Authentication Context
สามารถสร้าง test utility
src/test/test-utils.tsx
ตัวอย่าง
import {
render,
type RenderOptions,
} from '@testing-library/react'
import type { ReactElement } from 'react'
function Providers({
children,
}: {
children: React.ReactNode
}) {
return (
<>
{children}
</>
)
}
function customRender(
ui: ReactElement,
options?: RenderOptions
) {
return render(ui, {
wrapper: Providers,
...options,
})
}
export * from '@testing-library/react'
export {
customRender as render,
}
จากนั้น test สามารถใช้
import {
render,
screen,
} from '../test/test-utils'
แทนการประกาศ Provider ทุก test
#22. Code Coverage
ติดตั้ง
npm install -D @vitest/coverage-v8
เพิ่ม script
{
"scripts": {
"test:coverage":
"vitest run --coverage"
}
}
รัน
npm run test:coverage
Coverage โดยทั่วไปประกอบด้วย
Statements
Branches
Functions
Lines
ตัวอย่าง configuration
export default defineConfig({
test: {
environment: 'jsdom',
coverage: {
provider: 'v8',
reporter: [
'text',
'html',
],
include: [
'src/**/*.{ts,tsx}',
],
exclude: [
'src/main.tsx',
],
},
},
})
#23. ตัวอย่างโครงสร้าง Project
src/
├── components/
│ ├── Counter.tsx
│ ├── Counter.test.tsx
│ ├── LoginForm.tsx
│ ├── LoginForm.test.tsx
│ ├── UserCard.tsx
│ └── UserCard.test.tsx
│
├── services/
│ └── userService.ts
│
├── test/
│ ├── setup.ts
│ └── test-utils.tsx
│
├── App.tsx
└── main.tsx
vitest.config.ts
package.json
การวาง test ไว้ใกล้ Component ทำให้ค้นหาและดูแลรักษาได้ง่าย
#24. แนวทางการตั้งชื่อ Test
ควรอธิบาย behavior
ตัวอย่างที่ดี
it('shows error when email is invalid')
it('increments count when user clicks Increment')
it('calls onSubmit with entered email')
หลีกเลี่ยง
it('test button')
it('works correctly')
เพราะไม่สื่อว่า behavior ใดกำลังถูกตรวจสอบ
#25. แนวทางเลือก Query
โดยทั่วไปควรให้ความสำคัญประมาณนี้
1. getByRole
2. getByLabelText
3. getByPlaceholderText
4. getByText
5. getByDisplayValue
6. getByTestId
data-testid ใช้ได้ แต่ควรเป็นทางเลือกท้าย ๆ เมื่อไม่สามารถค้นหา element ด้วย semantics ได้เหมาะสม
ตัวอย่าง
<button>
Save
</button>
ควรใช้
screen.getByRole(
'button',
{ name: 'Save' }
)
มากกว่า
screen.getByTestId(
'save-button'
)
#26. userEvent กับ fireEvent
สำหรับพฤติกรรมผู้ใช้ทั่วไป แนะนำ
const user = userEvent.setup()
await user.click(button)
await user.type(input, 'hello')
await user.clear(input)
await user.tab()
เพราะ user-event จำลอง interaction หลายขั้นตอนให้ใกล้กับสิ่งที่เกิดขึ้นใน browser มากกว่า event เดี่ยว
fireEvent ยังมีประโยชน์เมื่อจำเป็นต้อง trigger DOM event แบบเฉพาะเจาะจง
#27. สิ่งที่ไม่ควรทดสอบมากเกินไป
ไม่ควรผูก test กับ implementation detail เช่น
ชื่อ state variable
ชื่อ private function
จำนวนครั้งที่ setState ทำงาน
โครงสร้าง DOM ที่ไม่เกี่ยวกับ behavior
ชื่อ CSS class ที่ไม่ได้เป็น requirement
ตัวอย่างไม่ดี
expect(
container.firstChild
).toHaveClass('card-v2')
ถ้า requirement จริงคือ
ผู้ใช้ต้องเห็นชื่อสินค้า
ควรทดสอบ
expect(
screen.getByText('MacBook')
).toBeInTheDocument()
#28. Component Test vs Unit Test vs E2E Test
| ระดับ | เป้าหมาย |
|---|---|
| Unit Test | Function / Logic |
| Component Test | UI Component และ interaction |
| Integration Test | หลายส่วนทำงานร่วมกัน |
| E2E Test | Workflow จริงจากหน้าเว็บถึง backend |
ตัวอย่าง
calculateTotal()
↓
Unit Test
ProductCard
↓
Component Test
ProductList + API State
↓
Integration Test
Login → Add Cart → Checkout
↓
E2E Test
Vitest เหมาะมากกับ
Unit Test
Component Test
Integration Test
ส่วน E2E Test มักใช้เครื่องมือ เช่น
Playwright
Cypress
#29. Testing Workflow ที่แนะนำ
Write Component
↓
Render Component
↓
Find Element
↓
Simulate User Action
↓
Assert Visible Behavior
↓
Mock External Dependency
↓
Run Coverage
↓
CI/CD
ตัวอย่าง CI
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm run test:run
- name: Run coverage
run: npm run test:coverage
#30. Best Practices
#1. Test จากมุมมองผู้ใช้
ถามว่า
ผู้ใช้เห็นอะไร?
ผู้ใช้กดอะไร?
หลังจากกดแล้วเกิดอะไร?
#2. ใช้ Accessible Query
เช่น
getByRole()
getByLabelText()
ช่วยให้ test รองรับแนวคิด accessibility ไปพร้อมกัน
#3. อย่า Mock ทุกอย่าง
Mock เฉพาะ dependency ที่จำเป็น เช่น
HTTP API
External Service
Time
Browser API
การ mock มากเกินไปทำให้ test ผ่านทั้งที่ระบบจริงอาจทำงานผิด
#4. Test Behavior มากกว่า Implementation
ทดสอบ
"คลิกแล้วแสดงผลลัพธ์"
ไม่ใช่
"setState ถูกเรียก"
#5. ให้แต่ละ Test มีเป้าหมายชัดเจน
Test ที่ดีควรอ่านแล้วเข้าใจ requirement ได้ทันที
#31. Checklist สำหรับ React Component Testing
ก่อนถือว่า Component มี test ที่ดี ควรตรวจสอบว่า
- [ ] Render Component ได้
- [ ] ตรวจสอบข้อความหรือ UI สำคัญ
- [ ] ตรวจสอบ Props
- [ ] ตรวจสอบ User Interaction
- [ ] ตรวจสอบ State Change ผ่าน UI
- [ ] ตรวจสอบ Callback
- [ ] ตรวจสอบ Form Input
- [ ] ตรวจสอบ Validation
- [ ] ตรวจสอบ Loading State
- [ ] ตรวจสอบ Error State
- [ ] ตรวจสอบ Async Behavior
- [ ] Mock External Dependency เมื่อจำเป็น
- [ ] ใช้ Accessible Query
- [ ] Test ไม่ผูกกับ implementation detail มากเกินไป
- [ ] มี Coverage Report
- [ ] รัน test ใน CI/CD
#สรุป
สำหรับ React Project ที่ใช้ Vite ชุดเครื่องมือ
Vitest
+
React Testing Library
+
user-event
+
jest-dom
+
jsdom
เป็น stack ที่เหมาะสำหรับ Component Testing เพราะสามารถทดสอบทั้ง
Rendering
Props
State
Events
Forms
Validation
Async UI
Callbacks
External Dependencies
แนวคิดสำคัญที่สุดคือ
ทดสอบสิ่งที่ผู้ใช้สามารถมองเห็นและกระทำกับ UI แทนการทดสอบ implementation ภายใน Component
ตัวอย่างรูปแบบ test ที่พบได้บ่อยที่สุดคือ
const user = userEvent.setup()
render(<Component />)
const button =
screen.getByRole(
'button',
{ name: 'Save' }
)
await user.click(button)
expect(
screen.getByText('Saved')
).toBeInTheDocument()
เมื่อ Component Testing ถูกออกแบบดี จะช่วยให้การ refactor React application ปลอดภัยขึ้น ลด regression และทำให้ CI/CD สามารถตรวจจับปัญหาได้ก่อน deploy
#References
- Vitest — Getting Started: https://vitest.dev/guide/
- Vitest — Test Environment: https://vitest.dev/guide/environment.html
- Vitest — Component Testing: https://vitest.dev/guide/browser/component-testing
- Vitest — Mocking: https://vitest.dev/guide/mocking
- Vitest — Coverage: https://vitest.dev/guide/coverage
- React Testing Library: https://testing-library.com/docs/react-testing-library/intro/
- Testing Library — Setup: https://testing-library.com/docs/react-testing-library/setup/
- jest-dom: https://github.com/testing-library/jest-dom