Vitest · Mocks และ Spies
Dependency Boundary
เข้าใจว่า mock ใช้แยก dependency ที่ช้า เปลี่ยนแปลงได้ หรือควบคุมยาก
Dependency boundary คือจุดที่ service ติดต่อสิ่งอื่น
Dependency คือสิ่งที่ function ต้องเรียกใช้แต่ไม่ได้เป็น logic หลักของตัวเอง เช่น repository, API client หรือ notifier ส่วน dependency boundary คือจุดเชื่อมต่อที่เราตัดสินใจได้ว่าจะใช้ของจริงหรือใช้ตัวแทนในการทดสอบ
| ควร mock เมื่อ | ไม่ควร mock เมื่อ |
|---|---|
| dependency ช้า, แพง หรือควบคุมผลลัพธ์ยาก | กำลังทดสอบ pure function ที่ไม่มี dependency |
| ต้องการจำลอง success/failure ที่เกิดยาก | mock เพียงเพื่อให้ test สั้นโดยไม่เข้าใจ behavior |
| คำถามสนใจการสื่อสารผ่าน boundary | mock ทุกชั้นจน test ไม่เหลือของจริงให้พิสูจน์ |
ทดลองเปลี่ยน dependency โดยไม่แตะ subject
ถ้า subject รับ repository ผ่าน parameter เราสามารถส่ง repository ตัวเล็กที่คืนข้อมูลคงที่เข้าไปได้ จุดนี้ทำให้เห็นความหมายของ boundary ก่อนรู้จัก vi.fn: subject ยังเป็นของจริง แต่แหล่งข้อมูลถูกควบคุมไว้
เมื่อมาถึงบท vi.fn เราจะใช้ function ตัวแทนที่นอกจากคืนค่าได้แล้ว ยังเก็บประวัติการเรียกเพื่อให้ตรวจ interaction ได้ด้วย
mock เฉพาะสิ่งที่อยู่นอกคำถาม
กฎสำคัญ
ให้ unit test คุม input ได้
mock repository ให้คืน Task ที่กำหนด เพื่อโฟกัส logic ของ service
อย่า mock function ที่กำลังทดสอบ
ถ้าแทน implementation ของ subject เอง test อาจตรวจแค่ mock ไม่ได้ตรวจ behavior จริง
dependency injection ช่วยให้ boundary ชัด
รับ dependency ผ่าน parameter หรือ object ทำให้เปลี่ยนตัวแทนได้โดยไม่ต้องใช้ module mock ขั้นสูง
mock เป็นเครื่องมือ ไม่ใช่คำตอบทุก test
ถ้า dependency ทำงานเร็วและ deterministic การใช้ของจริงอาจทำให้ test อ่านง่ายกว่า การ mock ควรมีเหตุผลจากคำถามของ test
แบบฝึก: ตัดสินใจว่าจะ mock อะไร
อ่าน service ที่รับ repository แล้วอธิบายว่าอะไรคือ subject และอะไรคือ dependency
งานที่ต้องทำ
- ระบุว่า logic ที่ต้องการทดสอบคืออะไร
- ระบุว่า repository อยู่ด้านนอกคำถามอย่างไร
- กำหนดผลลัพธ์ของ repository ที่ test ต้องควบคุม
ลองทำด้วยตัวเองก่อน แล้วค่อยเปิดเฉลยเพื่อเทียบแนวคิดและรายละเอียด