บทที่ 6 — Error & Fault Simulation¶
Pre-chapter Retrieval¶
ก่อนอ่านบทนี้ ลองตอบก่อนนะครับ:
- จะสร้าง stub ที่ delay 2 วินาทีก่อน return response ได้ยังไง?
- ต้องทำอะไรก่อนถึงจะใช้
{{now}}ใน response body ได้?
เฉลย
- เพิ่ม
"fixedDelayMilliseconds": 2000ใน response object - รัน WireMock ด้วย
--global-response-templatingหรือเพิ่ม"transformers": ["response-template"]ใน response
วัตถุประสงค์¶
อ่านจบแล้วคุณจะ:
- จำลอง HTTP error response ได้ (4xx, 5xx)
- จำลอง network fault ได้ (connection reset, empty response)
- ออกแบบ test scenario สำหรับ negative testing ได้
ทำไมต้องรู้? (Why)¶
Negative testing คือหัวใจของงาน QA — เราต้องทดสอบว่าแอปรับมือกับสถานการณ์เลวร้ายได้ดีแค่ไหน
ปัญหาคือในสภาพแวดล้อมทดสอบปกติ เราทำให้ API พังหรือ network ขาดได้ยากมาก WireMock แก้ปัญหานี้ได้ทันที — เราควบคุม "ความเลวร้าย" ได้เองทุกอย่าง
เนื้อหาหลัก¶
HTTP Error Responses¶
400 Bad Request:
{
"request": {
"method": "POST",
"urlPath": "/api/register"
},
"response": {
"status": 400,
"jsonBody": {
"error": "Bad Request",
"details": [
{ "field": "email", "message": "Invalid email format" },
{ "field": "password", "message": "Password must be at least 8 characters" }
]
},
"headers": { "Content-Type": "application/json" }
}
}
500 Internal Server Error:
{
"request": {
"method": "GET",
"urlPath": "/api/orders/crash"
},
"response": {
"status": 500,
"jsonBody": {
"error": "Internal Server Error",
"message": "An unexpected error occurred",
"traceId": "abc-123-xyz"
},
"headers": { "Content-Type": "application/json" }
}
}
503 Service Unavailable (จำลองเมื่อ server ไม่พร้อม):
{
"request": {
"method": "GET",
"urlPath": "/api/payments/status"
},
"response": {
"status": 503,
"jsonBody": {
"error": "Service Unavailable",
"message": "Payment service is temporarily unavailable"
},
"headers": {
"Content-Type": "application/json",
"Retry-After": "30"
}
}
}
Network Faults¶
นอกจาก HTTP errors ยังจำลอง network-level fault ได้ด้วย: (source: wiremock.org/docs/simulating-faults/)
Connection Reset — จำลองว่า server ตัด connection กะทันหัน:
{
"request": {
"method": "GET",
"urlPath": "/api/unstable"
},
"response": {
"fault": "CONNECTION_RESET_BY_PEER"
}
}
Empty Response — server รับ connection แต่ไม่ส่งอะไรกลับมาเลย:
{
"request": {
"method": "GET",
"urlPath": "/api/silent"
},
"response": {
"fault": "EMPTY_RESPONSE"
}
}
Fault types ทั้งหมด:
| Fault | จำลองอะไร | ใช้ทดสอบอะไร |
|---|---|---|
CONNECTION_RESET_BY_PEER |
server ตัด connection กะทันหัน | retry logic, error handling |
EMPTY_RESPONSE |
server รับ connection แต่ไม่ส่งอะไร | timeout handling |
MALFORMED_RESPONSE_CHUNK |
ส่ง response บางส่วนแล้วส่ง garbage | partial response handling |
RANDOM_DATA_THEN_CLOSE |
ส่ง random data แล้วปิด connection | data parsing error handling |
Timeout Simulation¶
จำลอง API ที่ใช้เวลานานจนเกิน timeout:
{
"request": {
"method": "POST",
"urlPath": "/api/process"
},
"response": {
"status": 200,
"jsonBody": { "result": "done" },
"headers": { "Content-Type": "application/json" },
"fixedDelayMilliseconds": 30000
}
}
delay 30 วินาที — ถ้า client มี timeout แค่ 10 วินาทีจะเกิด timeout error
ตัวอย่าง 3 ระดับ¶
Beginner — ทดสอบ validation error message¶
QA ต้องทดสอบว่าหน้า registration แสดง error message ถูกต้องเมื่อ email ซ้ำ:
{
"request": {
"method": "POST",
"urlPath": "/api/register",
"bodyPatterns": [
{ "matchesJsonPath": "$[?(@.email == 'existing@email.com')]" }
]
},
"response": {
"status": 409,
"jsonBody": {
"error": "Conflict",
"message": "Email already registered"
},
"headers": { "Content-Type": "application/json" }
}
}
Intermediate — ทดสอบ retry mechanism¶
แอปต้องลอง retry 3 ครั้งเมื่อ API return 503 ใน Scenario นี้ stub ตัวแรก return 503 เพื่อให้ทดสอบ retry:
mappings/flaky-api.json:
{
"request": {
"method": "GET",
"urlPath": "/api/flaky"
},
"response": {
"status": 503,
"jsonBody": {
"error": "Service temporarily unavailable"
},
"headers": {
"Content-Type": "application/json",
"Retry-After": "1"
}
}
}
ทดสอบว่า client code retry และแสดง error message ถูกต้องหลัง retry ครบ 3 ครั้ง
Advanced — จำลอง Cascading Failure¶
ระบบ microservice ที่ payment service ล่ม แล้วทำให้ order service ล้มตาม:
mappings/payment-down.json:
{
"request": {
"method": "POST",
"urlPath": "/api/payments/charge"
},
"response": {
"fault": "CONNECTION_RESET_BY_PEER"
}
}
mappings/order-payment-failed.json:
{
"request": {
"method": "POST",
"urlPath": "/api/orders",
"bodyPatterns": [
{ "matchesJsonPath": "$[?(@.paymentMethod == 'online')]" }
]
},
"response": {
"status": 503,
"jsonBody": {
"error": "Service Unavailable",
"message": "Payment service is unavailable. Please try again later.",
"fallbackOptions": ["cash_on_delivery", "bank_transfer"]
},
"headers": { "Content-Type": "application/json" }
}
}
ทดสอบว่า UI แสดงตัวเลือก fallback ให้ user ได้ถูกต้อง
Common Mistakes¶
❌ ใช้ 500 error สำหรับทุก error case 500 หมายถึง "server bug" ไม่ใช่ "user ทำผิด" — ถ้าใช้ 500 ทุกที่ developer อาจแก้ code ผิดทาง → ✅ เลือก status code ให้ถูกต้อง: validation error = 400/422, not found = 404, duplicate = 409, no permission = 403 (best practice)
❌ Test fault แล้ว hang ไม่สิ้นสุด
EMPTY_RESPONSE ทำให้ client รอ response ตลอดถ้าไม่มี timeout
→ ✅ ตั้ง timeout ที่ client side เสมอก่อน test fault scenarios (best practice)
❌ ลืมทดสอบ error message ที่แสดงต่อ user test แค่ว่า status code ถูกต้อง แต่ไม่ check ว่า UI แสดงข้อความที่ user เข้าใจได้ → ✅ เช็คทั้ง status code และ UI error message ทุกครั้ง (best practice)
สรุปบท¶
⏸ คำถาม Retrieval
-
Network fault ต่างจาก HTTP error response อย่างไร? ยกตัวอย่างว่าแต่ละแบบทดสอบอะไรได้บ้าง
-
ทดสอบ retry mechanism ของแอปได้อย่างไรโดยใช้ WireMock?
-
ถ้าต้องการทดสอบว่าแอป handle connection timeout ได้ถูกต้อง จะออกแบบ stub ยังไง?
เฉลย (คลิกเพื่อดู)
ข้อ 1: HTTP error (4xx, 5xx) คือ response ปกติที่ server ส่งกลับมาพร้อม error code — client รู้ว่าได้รับ response แล้ว | Network fault คือ connection level ปัญหา — client ไม่ได้รับ HTTP response เลย ใช้ทดสอบ error handling / retry ที่ transport layer
ข้อ 2: สร้าง stub ที่ return 503 แล้วดูว่า client retry กี่ครั้งและแสดงผลอย่างไร (อาจจับคู่กับ Scenario/stateful mock ถ้าต้องการให้ retry ครั้งที่ N สำเร็จ — จะเรียนในบท 9)
ข้อ 3: ใช้ "fixedDelayMilliseconds" ที่มากกว่า timeout ของ client เช่น client timeout 5 วินาที → ตั้ง delay 10 วินาที แล้วดูว่า client จัดการ timeout ถูกต้องหรือเปล่า