คู่มือพิมพ์เขียวเชิงลึกและการวิเคราะห์ทางเทคนิค: JSON Schema Matching, Partial JSON, Velocity & JavaScript Dynamic Templates, Action Types และ 6 Hands-on Drills
ชื่อบท/หัวข้อหลัก: Module 2: Advanced Expectation Matching (Matchers), Action Types (Response, Forward, Callback, Error) & Dynamic Response Logic (Velocity, JS, WASM)
ตอบ: ในระบบจริง ข้อมูลที่ส่งหากันไม่ได้มีแค่ข้อความตายตัว (Static Text) แต่มีความผันแปรตาม Input เช่น สกุลเงิน, ID ผู้ใช้, หรือเงื่อนไขทางธุรกิจ บทนี้ตั้งชื่อว่า Advanced Expectation Matching & Dynamic Response Logic เพราะการใช้ Static Mocks ที่ตอบกลับค่าเดิมๆ จะทำให้เกิดภาวะ Combinatorial Explosion (ต้องเขียน Mocks นับร้อยข้อสำหรับทุกกรณี) บทนี้จึงสอนวิธีใช้ Advanced Matchers และ Dynamic Templates เพื่อให้ Expectation เพียงข้อเดียวสามารถคำนวณและตอบกลับข้อมูลที่เปลี่ยนไปตาม Request ได้อย่างชาญฉลาด
ระบบชำระเงินข้ามชาติ (Global Multi-Currency Payment Gateway) กับปัญหา Static Mock บานปลาย
บริษัทอีคอมเมิร์ซเปิดขยายบริการไปยัง 15 ประเทศ ระบบต้องรองรับการตัดเงินในสกุลเงินที่หลากหลาย (THB, USD, EUR, JPY ฯลฯ) พร้อมทั้งคำนวณอัตราแลกเปลี่ยน ภาษีมูลค่าเพิ่มประจำประเทศ และออกสุ่มรหัสธุรกรรม (transaction_id) ที่ไม่ซ้ำกันกลับมา
ในตอนแรก ทีมพัฒนาใช้วิธีสร้าง Static Mock คืนค่า JSON ตรงๆ ผลคือ เมื่อต้องทดสอบเคสชำระเงิน 15 สกุลเงิน ร่วมกับสถานะผู้ใช้งาน 4 ประเภท ทีมงานต้องนั่งเขียน Expectation ถึง 60 ข้อ (15 x 4)! และเมื่อ API มีการปรับโครงสร้างเพียงฟีลด์เดียว ทีมงานต้องตามแก้ไขไฟล์ JSON ทั้ง 60 ไฟล์ ส่งผลให้โค้ดการทดสอบพังพินาศ
💡 การนำ Advanced Matchers & Dynamic Response เข้ามาแก้ปัญหา: ทีมงานปฏิวัติตัว Mock ใหม่ทั้งหมด โดยใช้ JSON Schema Matcher ตรวจสอบความถูกต้องของ Payload และใช้ Velocity Template Logic สกัดค่า amount, currency, และ userId จาก Request มาคำนวณภาษี สุ่มรหัส UUID แล้วตอบกลับเป็น JSON ไดนามิก ผลคือใช้ Expectation เพียงข้อเดียวแทนการเขียน 60 ข้อเดิม!
แปลตรงตัว: การเทียบโครงสร้าง JSON กับสเปกมาตรฐาน
อุปมาอุปไมย: เหมือน "ตะแกรงร่อนขนาดสินค้า" ที่ไม่สนใจว่าลูกบอลข้างในจะเป็นสีอะไร แต่ตรวจสอบว่าขนาดและรูปทรงของลูกบอล (Data Types, Required Fields) ตกผ่านช่องตะแกรงตามกฎมาตรฐานหรือไม่
แปลตรงตัว: การดักจับเฉพาะฟีลด์ที่สนใจ
อุปมาอุปไมย: เหมือน "เครื่องสแกนใบหน้า" ที่ตรวจเฉพาะดวงตาและจมูก แม้ผู้ใช้จะใส่หมวกหรือใส่ต่างหูเพิ่มมา (มีฟีลด์อื่นเพิ่มมาใน JSON) เครื่องก็ยังจำหน้าได้ถูกต้อง
แปลตรงตัว: เทมเพลตการตอบกลับแบบเปลี่ยนค่าได้ตามตรรกะ
อุปมาอุปไมย: เหมือน "พนักงานพิมพ์ดีดอัจฉริยะ" ที่หยิบกระดาษแบบฟอร์มเปล่ามา แล้วมองดูเอกสารที่ลูกค้าส่งมา จากนั้นคัดลอกชื่อ สกุลเงิน คำนวณเลขภาษีสดๆ แล้วพิมพ์ใส่แบบฟอร์มส่งกลับไปให้ทันที
แปลตรงตัว: ประเภทของการกระทำเมื่อ Match
อุปมาอุปไมย: เหมือน "สวิตช์เลือกโหมดทำงาน 4 ปุ่ม" ได้แก่ คืนค่า (httpResponse), ส่งต่อ (httpForward), สคริปต์ (httpCallback), และตัดสายพัง (httpError)
การเขียน Mock แบบ Shallow Modeling ที่ใช้ค่า Static ทำให้เกิดภาวะ Combinatorial Explosion คือไฟล์ตั้งค่า Mocks ขยายตัวตามจำนวน Test Cases จนไม่สามารถบำรุงรักษาได้
นักพัฒนาต้องเสียเวลาถึง 40% ไปกับการนั่งแก้ไขไฟล์ Static Mocks แทนที่จะได้นำเวลาไปพัฒนาฟีเจอร์ทางธุรกิจที่มีมูลค่า
body.jsonSchema)
{
"httpRequest": {
"method": "POST",
"path": "/api/v2/register",
"body": {
"type": "JSON_SCHEMA", // [Line 5] ระบุเอนจินการตรวจจับเป็น JSON_SCHEMA
"jsonSchema": "{\"$schema\": \"http://json-schema.org/draft-07/schema#\", \"type\": \"object\", \"properties\": {\"email\": {\"type\": \"string\"}, \"age\": {\"type\": \"integer\", \"minimum\": 18}}, \"required\": [\"email\", \"age\"]}" // [Line 6] กำหนดสเปก: email ต้องเป็น string, age ต้อง >= 18 และทั้งคู่เป็นฟีลด์บังคับ
}
}
}
httpResponseTemplate)
{
"httpResponseTemplate": {
"templateType": "VELOCITY", // [Line 3] ใช้ Apache Velocity Engine ในการประมวลผล
"template": "{\n \"statusCode\": 200,\n \"headers\": { \"Content-Type\": [\"application/json\"] },\n \"body\": \"{\\\"transactionId\\\": \\\"TX-$uuid.random()\\\", \\\"chargedAmount\\\": $request.body.amount, \\\"currency\\\": \\\"$request.body.currency\\\", \\\"vat7\\\": $request.body.amount * 0.07}\"\n}"
// [Line 4] $uuid.random() สุ่ม UUID, $request.body.amount ดึงยอดเงินจาก Request มาคำนวณภาษี 7% สดๆ
}
}
templateType: JAVASCRIPT)
{
"httpResponseTemplate": {
"templateType": "JAVASCRIPT", // [Line 3] ใช้ JavaScript Engine (Nashorn/GraalVM)
"template": "var body = JSON.parse(request.body);\nvar result = body.score >= 700 ? 'APPROVED' : 'REJECTED';\nreturn {\n 'statusCode': 200,\n 'headers': { 'Content-Type': ['application/json'] },\n 'body': JSON.stringify({\n 'applicant': body.name,\n 'score': body.score,\n 'status': result\n })\n};"
// [Line 4] สกัดอ่าน score จาก Request Body มาตัดเกรดผ่าน/ไม่ผ่านด้วย Ternary Operator ใน JS
}
}
เปิดเว็บเบราว์เซอร์ไปที่: http://localhost:1080/mockserver/dashboard
========================================================================================
MOCKSERVER WEB DASHBOARD (PORT 1080)
========================================================================================
[ Active Expectations ] | [ Recorded Requests / Logs ] | [ Breakpoints ] | [ Reset All ]
----------------------------------------------------------------------------------------
Active Expectations Tab: - ตรวจสอบว่าการดักจับในช่อง httpRequest เป็นประเภท JSON_SCHEMA, REGEX หรือ ONLY_MATCHING_FIELDS
- ตรวจดูว่าช่อง httpResponseTemplate แสดงเอนจิน VELOCITY หรือ JAVASCRIPT ครบถ้วน
Recorded Requests & Logs: - หากยิง Request แล้วได้รับ 404 Not Found ให้เปิดแท็บ Recorded Requests / Logs
- ระบบจะแสดงรายละเอียด Mismatch สาเหตุอย่างชัดเจน เช่น "Body did not match JSON Schema: age is less than minimum"
mockserver-module2-enterprise/
├── docker-compose.yml
├── config/
│ └── mockserver.properties
├── expectations/
│ ├── auth/
│ │ └── regex-jwt-matcher.json # ดักจับ Regex Path & Headers
│ ├── payments/
│ │ ├── velocity-dynamic-payment.json # คำนวณยอดเงินและภาษีด้วย Velocity
│ │ └── schema-validation-order.json # ตรวจสเปกด้วย JSON Schema
│ └── credit/
│ └── javascript-conditional.json # ตรรกะเงื่อนไขซับซ้อนด้วย JavaScript
├── scripts/
│ └── seed_module2_expectations.sh
└── tests/
└── AdvancedMatchingIntegrationTest.java
ต้องสร้างไฟล์แยกทีละข้อสำหรับ THB, USD, EUR, JPY...
{
"httpRequest": { "method": "POST", "path": "/charge", "body": "{\"currency\":\"THB\",\"amount\":100}" },
"httpResponse": { "body": "{\"currency\":\"THB\",\"vat\":7}" }
}
ข้อเดียวรับได้ทุกสกุลเงินและคำนวณไดนามิกอัตโนมัติ:
{
"httpRequest": { "method": "POST", "path": "/charge" },
"httpResponseTemplate": {
"templateType": "VELOCITY",
"template": "{\"body\": \"{\\\"currency\\\": \\\"$request.body.currency\\\", \\\"vat\\\": $request.body.amount * 0.07}\"}"
}
}
ก่อนเริ่มทำ ตรวจสอบว่าเปิด MockServer ไว้ที่ http://localhost:1080 เรียบร้อยแล้ว
Setup:
curl -X PUT "http://localhost:1080/mockserver/expectation" -H "Content-Type: application/json" -d '{"httpRequest": { "method": "POST", "path": "/api/v2/register", "body": { "type": "JSON_SCHEMA", "jsonSchema": "{\"$schema\": \"http://json-schema.org/draft-07/schema#\", \"type\": \"object\", \"properties\": {\"email\": {\"type\": \"string\"}, \"age\": {\"type\": \"integer\", \"minimum\": 18}}, \"required\": [\"email\", \"age\"]}" } }, "httpResponse": { "statusCode": 201, "headers": { "Content-Type": ["application/json"] }, "body": "{\"status\": \"REGISTERED\"}" }}'
Test & Expected: ผ่าน: curl -i -X POST http://localhost:1080/api/v2/register -H "Content-Type: application/json" -d '{"email": "[email protected]", "age": 20}' (ได้ 201 Created) | ไม่ผ่าน: curl -i -X POST http://localhost:1080/api/v2/register -H "Content-Type: application/json" -d '{"email": "[email protected]", "age": 15}' (ได้ 404)
Setup:
curl -X PUT "http://localhost:1080/mockserver/expectation" -H "Content-Type: application/json" -d '{"httpRequest": { "method": "POST", "path": "/api/v2/search", "body": { "type": "JSON", "json": "{\"filter\": \"active\"}", "matchType": "ONLY_MATCHING_FIELDS" } }, "httpResponse": { "statusCode": 200, "headers": { "Content-Type": ["application/json"] }, "body": "{\"results\": [\"Item 1\"]}" }}'
Test & Expected: curl -i -X POST http://localhost:1080/api/v2/search -H "Content-Type: application/json" -d '{"filter": "active", "page": 5}' (ได้ 200 OK แม้มีฟีลด์ page เพิ่มมา)
Setup:
curl -X PUT "http://localhost:1080/mockserver/expectation" -H "Content-Type: application/json" -d '{"httpRequest": { "method": "GET", "path": { "type": "REGEX", "regex": "/api/v2/users/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}" }, "headers": { "Authorization": ["Bearer jwt\\..*"] } }, "httpResponse": { "statusCode": 200, "headers": { "Content-Type": ["application/json"] }, "body": "{\"profile\": \"Valid UUID\"}" }}'
Test & Expected: curl -i http://localhost:1080/api/v2/users/123e4567-e89b-12d3-a456-426614174000 -H "Authorization: Bearer jwt.xyz123" (ได้ 200 OK)
Setup:
curl -X PUT "http://localhost:1080/mockserver/expectation" -H "Content-Type: application/json" -d '{"httpRequest": { "method": "POST", "path": "/api/v2/checkout" }, "httpResponseTemplate": { "templateType": "VELOCITY", "template": "{\n \"statusCode\": 200,\n \"headers\": { \"Content-Type\": [\"application/json\"] },\n \"body\": \"{\\\"orderId\\\": \\\"ORD-$uuid.random()\\\", \\\"finalAmount\\\": $request.body.amount * 0.9}\"\n}" }}'
Test & Expected: curl -i -X POST http://localhost:1080/api/v2/checkout -H "Content-Type: application/json" -d '{"amount": 2000}' (ได้ finalAmount: 1800.0 พร้อมสุ่ม UUID orderId)
Setup:
curl -X PUT "http://localhost:1080/mockserver/expectation" -H "Content-Type: application/json" -d '{"httpRequest": { "method": "POST", "path": "/api/v2/credit-check" }, "httpResponseTemplate": { "templateType": "JAVASCRIPT", "template": "var body = JSON.parse(request.body);\nvar res = body.score >= 700 ? 'APPROVED' : 'REJECTED';\nreturn {'statusCode': 200, 'headers': {'Content-Type': ['application/json']}, 'body': JSON.stringify({'status': res})};" }}'
Test & Expected: curl -i -X POST http://localhost:1080/api/v2/credit-check -H "Content-Type: application/json" -d '{"name": "Somchai", "score": 750}' (ได้ "status": "APPROVED")
Setup:
curl -X PUT "http://localhost:1080/mockserver/expectation" -H "Content-Type: application/json" -d '{"httpRequest": { "method": "GET", "path": "/api/v2/unstable" }, "httpError": { "dropConnection": true }}'
Test & Expected: curl -i http://localhost:1080/api/v2/unstable (ได้ curl: (52) Empty reply from server ทันที)
คลิกเลือกคำตอบข้อที่ถูกต้อง ระบบจะตรวจให้คะแนนและแสดงผลเฉลยพร้อมคำอธิบายทันที!
คะแนนของคุณคือ ...
เนื้อหาและแบบฝึกหัดทั้งหมดในวันนี้ ตอบและสะท้อนชื่อบท Day 2 / Module 2: Advanced Expectation Matching, Action Types & Dynamic Response Logic อย่างสมบูรณ์: การใช้ Advanced Matchers (เช่น JSON Schema) ช่วยให้เรากรอง Request ได้อย่างแม่นยำ, การเลือก Action Types ที่เหมาะสมทำให้ควบคุมทิศทางของ Response ได้หลากหลาย และการใช้ Dynamic Response Logic (Velocity/JS) คือหัวใจสำคัญที่ช่วยให้ Expectation เพียงข้อเดียวรับมือกับข้อมูลนับพันรูปแบบได้โดยไม่ต้องนั่งเขียน Static Mocks ซ้ำซ้อนครับ!