#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 โดยเฉพาะ

ข้อดีหลัก ได้แก่

  1. ใช้ configuration และ module resolution ใกล้เคียงกับ Vite
  2. รองรับ TypeScript และ JSX/TSX
  3. รองรับ Watch Mode
  4. มี API เช่น describe, it, expect, vi.fn, vi.mock
  5. รองรับ Snapshot
  6. รองรับ Code Coverage ผ่าน V8 หรือ Istanbul
  7. รองรับทั้ง 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