Back to notes
mastery-frontend-react
Featured

React Testing Library: เลิก Test รายละเอียดโค้ด แล้วมา Test ความรู้สึก User

เขียน Test ให้เหมือนคนใช้งานจริง! เจาะลึกปรัชญาการเขียน Test ที่ทนทานต่อการ Refactor เลิกหา Component ด้วย ClassName และเปลี่ยนมาใช้ระบบ Accessibility Role แทน

January 30, 20263 min read readNNexis by Seereen

🛑 1. The Problem First: "Test ที่เขียวแต่พัง และ Test ที่พังแต่แอปปกติ"

ลองนึกถึงวันที่คุณเขียน Test เพื่อตรวจสอบการกดปุ่ม:

HLJS TSX
// ❌ Naive Approach: Test ตามโครงสร้างโค้ด (Brittle Test)
const { container } = render(<MyButton />);
const btn = container.querySelector('.btn-blue-primary');
expect(btn.innerHTML).toBe('Click me');
// 🌋 พัง! วันรุ่งขึ้นคุณเปลี่ยน CSS จากสีฟ้าเป็นสีแดง (.btn-red)
# แม้ปุ่มจะยังทำงานได้ปกติ แต่ Test กลับ 'พัง' ทันทีเพราะคุณไปยึดติดกับชื่อ Class
# นี่คือสาเหตุที่หลายทีมมองว่าการเขียน Test คือภาระที่เสียเวลาครับ

ปัญหา: การเขียน Test ที่ยึดติดกับ "วิธีการ" (Implementation Details) เช่น ชื่อตัวแปร, ลำดับของ Div, หรือชื่อ Class จะทำให้ทุุกครั้งที่คุณ Refactor โค้ดให้สะอาดขึ้น คุณต้องเสียเวลามานั่งแก้ Test ตามไปด้วย จนสุดท้ายทุุกคนก็เลิกเขียน Test ไปในที่สุดครับ


💡 2. Real-Life Analogy: การตรวจรับรถยนต์จากโรงงาน

  • White-box Test (แบบเก่า): เหมือนคุณมุดลงไปใต้ท้องรถเพื่อเช็คว่า "น็อตตัวที่ 5 เป็นสีเงินไหม?" (ถ้าโรงงานเปลี่ยนเป็นน็อตสีดำที่แข็งแรงกว่าเดิม คุณจะบอกว่ารถคันนี้พังทันที ทั้งที่รถมันดีขึ้น)
  • Behavioral Test (RTL Philosophy): เหมือนคุณนั่งบนเบาะคนขับแล้วลอง "เหยียบเบรก" ดูว่ารถหยุดไหม? หรือ "หมุนกุญแจ" แล้วเครื่องติดไหม? (ไม่ว่าข้างในจะใช้สายไฟสีอะไร ตราบใดที่เหยียบเบรกแล้วรถหยุด รถคันนี้คือรถที่ผ่านการทดสอบ)
  • Accessibility Roles: เหมือนการมองหาปุ่มโดยใช้สัญชาตญาณคนทั่วไป "มองหาปุ่มที่เขียนว่า Login" แทนที่จะบอกว่า "มองหาวัตถุที่อยู่พิกัด X, Y"

🚀 3. Execution Journey: มหากาพย์การสร้าง Test ที่คงกระพัน

Senior จะเขียน Test ที่จะยัง 'เขียว' (ผ่าน) ตราบใดที่หน้าตาและพฤติกรรมของ User ยังเหมือนเดิม

🛠 Step-by-step:

  1. The Role Search: ใช้ screen.getByRole เสมอ (เช่น button, heading, textbox) เพื่ออ้างอิงถึงความหมายของ UI ไม่ใช่แค่หน้าตา
  2. The User Event: ใช้ @testing-library/user-event แทน fireEvent เพื่อจำลองทุุกขั้นตอนที่คนทำจริงๆ (เช่น มีการ Focus ก่อนพิมพ์)
  3. The Async Wait: ใช้ findBy... (ที่มี await) เมื่อต้องรอข้อมูลจาก API เพื่อจัดการกับสถานะ loading อย่างถูกต้อง
  4. The Snapshot Guard: ใช้ Snapshot เฉพาะส่วนที่ต้องการคุมความนิ่งของ UI จริงๆ ไม่ใช่ครอบทั้้งหน้าจนหาจุดผิดไม่เจอ
HLJS TSX
// ✅ Best Practice: การเขียน Test แบบ Senior (Behavioral)
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";

test("ผู้ใช้ต้องสามารถ Login ได้สำเร็จเมื่อกรอกข้อมูลครบ", async () => {
  render(<LoginForm />);

  // 🛠 ค้นหาด้วยความหมาย (Accessibility)
  await userEvent.type(screen.getByLabelText(/อีเมล/i), "admin@test.com");
  await userEvent.click(screen.getByRole("button", { name: /เข้าสู่ระบบ/i }));

  // 🛠 ตรวจสอบผลลัพธ์ที่ User สัมผัสได้จริง
  expect(await screen.findByText(/ยินดีต้อนรับ/i)).toBeInTheDocument();
});

🪤 4. The Junior Trap: โรค "TestId Overuse"

จูเนียร์มักจะขี้เกียจหาชื่อ Role เลยประทับตรา data-testid ลงไปทุุกจุด:

HLJS TSX
// ❌ Junior Trap: สาด TestId ไปทั่ว Component
<div data-testid="user-container">
   <button data-testid="submit-btn" />
</div>
// 🌋 พัง! การใช้ TestId มากเกินไปทำให้โค้ดเราสกปรกและ 'ทาบไปกับพฤติกรรมจริง' ไม่ได้
# เพราะ User ตัวจริงไม่มีทางเห็น data-testid พวกนี้ เขาเห็นแค่ปุ่มและข้อความ

ระวัง: data-testid คือทางออกสุดท้ายเมื่อคุณไม่สามารถหาของด้วยวิธีอื่นได้จริงๆ ✅ การแก้ไข: จงพยายามปรับปรุง HTML ให้มีความหมาย (Semantic) จนกระทั่งคุณสามารถหาของด้วย getByRole หรือ getByLabelText ได้ครับ


⚖️ 5. The Why Matrix: Implementation Test vs Behavioral Test

หัวข้อImplementation Test (พังง่าย)Behavioral Test (Senior)
ความทนทาน🐢 ต่ำ (แก้โค้ดนิดเดียว Test พัง)🚀 สูงสุด (ทนต่อการ Refactor)
ความเชื่อมั่นปานกลาง (อาจจะผ่านแต่แอปพังจริง)🚀 สูงมาก (ถ้าผ่านคือ User ใช้งานได้)
ความเร็วในการรัน⚡ เร็วมากปานกลาง (มีการจำลอง Event)
เอกสาร (Documentation)อ่านไม่รู้เรื่องว่าแอปทำอะไร😍 ดีมาก (อ่าน Test แล้วรู้เลยว่า App ทำงานยังไง)

🎓 6. Senior Mindset Summary

การเป็น Senior คือการมองว่า "เราเขียน Test เพื่อให้เรากล้าเปลี่ยนโค้ด ไม่ใช่เปลี่ยนเพื่อให้ Test มันผ่าน". Test ที่ดีคือเพื่อนคู่ใจที่จะบอกคุณว่า "ลุยเลย! คุณจัดระเบียบโค้ดได้เต็มที่ พฤติกรรมทุุกอย่างยังปกติดี" นั่นคือหัวใจของความมั่นคงในระยะยาวครับ!

Share this note

Related notes

View all notes
mastery-frontend-react
Basic

Modern Data Fetching: ทิ้ง Redux แล้วมาซบอก React Query

รู้จัก TanStack Query (React Query) อาวุธลับในการจัดการ Server State ที่จะทำให้โค้ด data fetching หายไป 90% พร้อมเทคนิคการจัดการ Cache และ Invalidation ขั้นเซียน

January 30, 20262 min read
mastery-frontend-react
Advanced

Scalable React Architecture: จัดโครงสร้าง Folder ยังไงไม่ให้โปรเจกต์ระเบิด

เลิกกองทุุกอย่างไว้ที่ components/! เจาะลึกโครงสร้างแบบ Feature-based, เทคนิค Colocation และการออกแบบระบบให้พร้อมเติบโตได้เป็นเวลา 10 ปีโดยไม่เป็นภาระของลูกหลาน

January 30, 20262 min read
mastery-frontend-react
Advanced

React Under the Hood: Virtual DOM และ Reconciliation ทำงานยังไง?

ไม่ได้มีไว้ท่องจำ! เจาะลึกกลไกภายในของ React เข้าใจ Diffing Algorithm และ Fiber Architecture ว่าทำไม React ถึงเป็น Framework ที่ครองโลก พร้อมวิธีเขียนโค้ดให้สอดคล้องกับเครื่องยนต์หลัก

January 30, 20262 min read

© 2026 My Notes by Seereen