Supertest · เริ่มต้นกับ Supertest
Todo API Contract
กำหนด resource, endpoint และ response contract ที่จะใช้เป็น domain กลางของ track
HTTP contract คือข้อตกลงระหว่าง client กับ API
HTTP contract คือข้อตกลงสาธารณะว่า client จะส่ง request แบบใด และ API จะตอบกลับอย่างไร ข้อตกลงนี้ประกอบด้วย method, path, input, status, headers, response body และ error shape การเขียน E2E test ที่ดีเริ่มจาก contract ก่อนเริ่มเขียน syntax ของ Supertest
| ส่วนของ contract | คำถามที่ต้องตอบ | ตัวอย่าง Todo API |
|---|---|---|
| Method | กำลังอ่าน สร้าง แก้ หรือ ลบอะไร | POST |
| Path | resource อยู่ที่ URL ใด | `/todos` |
| Input | ส่งข้อมูลผ่าน path/query/body/header อย่างไร | `{ title: string }` |
| Status | สำเร็จหรือผิดพลาดด้วยรหัสใด | 201, 400, 404 |
| Headers | client ต้องอ่าน metadata ใด | `content-type` |
| Body | ข้อมูลที่ caller ใช้งานได้มี shape ใด | `{ id, title, completed }` |
นี่คือ contract กลางที่ใช้เป็น domain เดียวกันตลอด track เพื่อให้ผู้เรียนโฟกัสที่ Supertest
contract กลางไม่ใช่กฎของทุก API
path และ status ในตัวอย่างเป็นข้อตกลงของ Todo API ในบทเรียน ให้ปรับ test ตาม contract จริงของ project ที่กำลังทำงาน อย่าเปลี่ยน expected เพียงเพื่อให้ test ผ่านโดยไม่ตรวจ API contract
อ่าน success response จาก public fields
Todo ใน track นี้มี `id`, `title`, `description` ที่เป็น optional และ `completed` เป็น boolean เมื่อสร้าง Todo สำเร็จ response ต้องเปิดเผย field ที่ client ใช้งานได้ ส่วน field ภายใน เช่น database timestamp หรือ relation ที่ไม่ได้อยู่ใน contract ไม่ควรถูก assert แบบละเอียดในทุก test
ใช้ `expect.objectContaining` เมื่อ contract สนใจเฉพาะ public fields ที่ระบุไว้ และไม่ต้องผูก test กับ field ภายในที่ไม่ได้เป็น contract
`completed: false` เป็น default behavior ที่ผู้สร้าง resource และ client ควรรู้จัก
วางแผน error contract ก่อนเขียน assertion
| กรณี | status ที่คาดหวัง | หลักฐานที่ควรตรวจ |
|---|---|---|
| สร้างสำเร็จ | 201 | public fields และ default state |
| payload ไม่ผ่าน validation | 400 | status และ error message/shape ตาม contract |
| ไม่พบ Todo | 404 | status และ error shape ที่ caller รู้จัก |
| ชื่อซ้ำหรือ conflict | 409 | แยก conflict ออกจาก server error |
| ไม่มีสิทธิ์หรือ token ใช้ไม่ได้ | 401/403 | จะลงรายละเอียดใน phase Authentication ภายหลัง |
shape จริงอาจต่างกันตาม exception filter หรือ framework configuration ให้ยึด public contract ของ project เป็นหลัก
อย่า assert error ด้วยข้อความที่ละเอียดเกินความจำเป็นถ้า contract ไม่ได้สัญญาข้อความนั้น เพราะการเปลี่ยน wording เล็กน้อยไม่ควรทำให้ test ของ behavior ที่ยังถูกต้องพัง ให้ตรวจเฉพาะ status และ field ที่ caller ใช้งานจริง
แปลง contract เป็น test intent
Test intent คือประโยคสั้น ๆ ที่บอก behavior ที่ต้องการพิสูจน์ ก่อนเขียน `.post()`, `.send()` หรือ `expect()` ให้เขียนประโยคที่มีเงื่อนไข การกระทำ และผลลัพธ์ วิธีนี้ช่วยให้เลือก assertion ได้จาก contract แทนการเขียนตาม syntax ที่จำได้
อ่าน test จาก intent ก่อน แล้วจึงจับคู่แต่ละส่วนกับ method, input, status และ response field
กฎสำคัญ
method และ path ต้องมาจาก contract
`POST /todos` กับ `GET /todos` เป็นคนละ behavior แม้ใช้ resource เดียวกัน
ตรวจ status ก่อนรายละเอียด
status ช่วยบอกผลระดับ protocol ก่อนที่เราจะอ่าน body ต่อ
assert public fields ที่จำเป็น
test ควรทนต่อ refactor ภายใน แต่ต้อง fail เมื่อ contract ที่ caller พึ่งพาถูกเปลี่ยน