การทดสอบที่พึ่งพา API จริงมักล้มเหลวด้วยเหตุผลที่ไม่เกี่ยวกับโค้ดของคุณ เช่น staging ล่ม, third-party rate limit, หรือข้อมูลถูกเปลี่ยนโดยทีมอื่น การจำลอง API (API mocking) แก้ปัญหานี้โดยแทนที่ endpoint จริงด้วย endpoint ที่ควบคุมได้ และส่ง response ตามที่คุณกำหนดทุกครั้ง
คู่มือนี้แสดง workflow แบบลงมือทำสำหรับการจำลอง API เพื่อใช้ในการทดสอบ: กำหนด schema, สร้าง mock response, รัน mock server, ชี้ test ไปยัง mock URL และทดสอบ error path ที่ API จริงมักไม่สร้างให้ตามต้องการ ตัวอย่างใช้ API จัดการคำสั่งซื้อขนาดเล็ก แต่แนวทางเดียวกันใช้ได้กับ REST หรือ GraphQL API ส่วนใหญ่
เมื่อการจำลอง API เป็นทางเลือกที่ถูกต้อง
ใช้ API mocking เมื่อเป้าหมายคือทดสอบโค้ดของคุณเอง ไม่ใช่ทดสอบเครือข่ายหรือ availability ของ service จริง เช่น unit test และ integration test ที่ต้องตรวจว่า client:
- parse response
200ได้ถูกต้อง - retry เมื่อเจอ
503 - แสดง error ที่เข้าใจได้เมื่อเจอ
404 - handle timeout หรือ malformed response ได้ปลอดภัย
แต่ไม่ควร mock ทุกอย่างเสมอไป ให้เก็บ test จำนวนเล็กน้อยสำหรับ contract test และ end-to-end test ที่เรียก API จริง เพื่อยืนยันว่า mock ยังตรงกับ production contract อยู่
หลักคิดสั้นๆ คือ:
- Mock เพื่อความเร็ว ความเสถียร และ isolation
- ใช้ API จริง เพื่อยืนยัน contract
อ่านเพิ่มได้ที่ สถานการณ์ที่การจำลอง API ได้ผล และ เซิร์ฟเวอร์จำลองกับเซิร์ฟเวอร์จริง
Workflow 5 ขั้นตอนสำหรับ API Mocking
ไม่ว่าจะใช้ภาษาใดหรือ test framework ใด ขั้นตอนหลักเหมือนกัน:
- กำหนด schema เพื่อให้ mock response ตรงกับรูปแบบจริง
- สร้าง mock response แบบ static หรือ dynamic
- รัน mock server เพื่อ expose response ผ่าน URL
- ชี้ test ไปยัง mock server โดยทำให้ base URL configurable
-
ทดสอบ error path เช่น
404,429,500, timeout และ malformed JSON
ตัวอย่างในบทความนี้ใช้ endpoint:
GET /orders/{id}
ขั้นตอนที่ 1: กำหนด Schema
Mock จะมีประโยชน์ก็ต่อเมื่อ response shape ใกล้เคียงของจริง เริ่มจาก schema ก่อน หากมีเอกสาร OpenAPI อยู่แล้ว ให้ใช้ไฟล์นั้น ถ้ายังไม่มี ให้เขียนเฉพาะ endpoint ที่ต้องทดสอบก่อน
ตัวอย่าง schema สำหรับ GET /orders/{id}:
paths:
/orders/{id}:
get:
parameters:
- name: id
in: path
required: true
schema: { type: string }
responses:
'200':
content:
application/json:
schema:
type: object
properties:
id: { type: string }
status: { type: string, enum: [pending, shipped, delivered] }
total: { type: number }
items: { type: array, items: { type: object } }
'404':
description: Order not found
Schema ทำหน้าที่สองอย่าง:
- บอก mock server ว่าควรสร้าง field อะไร
- เป็น single source of truth ของ contract ระหว่าง frontend/client กับ backend
เมื่อ backend เปลี่ยน contract เช่น เปลี่ยน total เป็น amount คุณต้อง update schema แล้ว regenerate หรือ update mock ให้ตรงกัน แนวทาง schema-first นี้ช่วยให้ การทดสอบสัญญา API น่าเชื่อถือขึ้น
ขั้นตอนที่ 2: สร้าง Mock Response
การสร้าง response มีสองแนวทางหลัก
Static response
Static response คือ JSON ที่กำหนดตายตัว เหมาะกับ test ที่ต้องการ assert ค่าแน่นอน
{
"id": "order_8842",
"status": "shipped",
"total": 149.99,
"items": [
{
"sku": "sku_001",
"name": "Keyboard",
"quantity": 1
},
{
"sku": "sku_002",
"name": "Mouse",
"quantity": 1
}
]
}
เหมาะสำหรับ test เช่น:
- client แสดงสถานะ
shippedถูกต้อง - total ถูก format เป็นสกุลเงิน
- items ถูก render ครบ
Dynamic response
Dynamic response ถูกสร้างตาม request เช่น generate id, random status จาก enum หรือสร้าง total ที่ดูสมจริง วิธีนี้ช่วยเจอ bug ที่ static payload อาจซ่อนไว้ เช่น parser พังเมื่อ string ยาว, field เป็น null, หรือ array มีจำนวน item มากกว่าปกติ
ทีมส่วนใหญ่มักใช้ทั้งสองแบบ:
- static response สำหรับ assertion-heavy tests
- dynamic response สำหรับ fuzz-style coverage
ด้วย Apidog คุณสามารถให้เครื่องมืออ่าน schema แล้วสร้าง mock endpoint ให้อัตโนมัติ รวมถึง map field name เช่น email, phone, avatar ไปเป็นข้อมูลตัวอย่างที่เหมาะสม
แนวทางสำคัญคืออย่าใช้ mock data ที่ง่ายเกินไป เช่น:
{
"id": "1",
"status": "shipped",
"total": 0,
"items": []
}
payload แบบนี้อาจทำให้ test ผ่าน แต่ไม่ได้สะท้อน production จริง ควรใช้ข้อมูลที่ใกล้เคียงของจริง เช่น มี item 2-3 รายการ, total สมจริง และ status อยู่ใน enum จริง
ขั้นตอนที่ 3: รัน Mock Server
เมื่อมี schema และ response แล้ว ต้องมี mock server สำหรับให้ test เรียกใช้งาน
Local mock server
เหมาะกับ unit test และ integration test เพราะเร็ว ทำงาน offline ได้ และไม่มี shared state ระหว่าง developer หรือ CI build
ตัวอย่างใช้ Prism:
prism mock openapi.yaml
# Mock server listening on http://127.0.0.1:4010
จากนั้นเรียก endpoint ได้ เช่น:
curl http://127.0.0.1:4010/orders/order_8842
Cloud mock server
Cloud mock server มี public URL เหมาะเมื่อ:
- mobile app ต้องเรียก mock API จาก device จริง
- CI runner เข้าไม่ถึงเครื่อง local
- external collaborator ต้องทดสอบ API เดียวกัน
- ต้อง demo API ก่อน backend พร้อม
Apidog มี Cloud Mock URL สำหรับ project ทำให้ทีมเรียก endpoint เดียวกันได้โดยไม่ต้องรัน server บนเครื่องตัวเอง
สำหรับ automated test ให้เริ่มจาก local mock เป็นค่า default เพราะเร็วและ predictable กว่า ส่วน cloud mock เหมาะกับ demo, cross-device testing และ collaboration
ขั้นตอนที่ 4: ชี้ Test ไปยัง Mock Server
อย่า hardcode production URL ใน test ให้ทำ base URL เป็น configuration ผ่าน environment variable แทน
ตัวอย่าง JavaScript test:
// orderClient.test.js
import { getOrder } from './orderClient.js';
const BASE_URL = process.env.API_BASE_URL || 'http://127.0.0.1:4010';
test('parses a shipped order', async () => {
const order = await getOrder('order_8842', BASE_URL);
expect(order.status).toBe('shipped');
expect(typeof order.total).toBe('number');
});
ตัวอย่าง client function:
// orderClient.js
export async function getOrder(id, baseUrl) {
const response = await fetch(`${baseUrl}/orders/${id}`);
if (response.status === 404) {
throw new Error('Order not found');
}
if (!response.ok) {
throw new Error(`Request failed: ${response.status}`);
}
return response.json();
}
ในเครื่อง local ใช้ค่า default:
npm test
ใน CI กำหนด API_BASE_URL ก่อนรัน test:
API_BASE_URL=http://127.0.0.1:4010 npm test
pattern นี้ทำให้โค้ด production ไม่ต้องรู้เลยว่ากำลังเรียก mock หรือ API จริง และใช้แนวคิดเดียวกันได้เมื่อคุณ ทำให้การทดสอบ API เป็นอัตโนมัติใน CI/CD
ขั้นตอนที่ 5: ทดสอบ Error Path
นี่คือจุดที่ mock API มีประโยชน์มาก เพราะ server จริงไม่ค่อยส่ง 500, 429 หรือ response ช้าตามเวลาที่คุณต้องการ แต่ mock server ทำได้ทันที
กำหนด scenario ที่ต้องทดสอบให้ชัดเจน:
| สถานการณ์ | Mock ส่งคืน | สิ่งที่ควร assert |
|---|---|---|
| ไม่พบ order | 404 |
client ส่ง error “ไม่พบ” ที่ชัดเจน |
| server ล้มเหลว | 500 |
client retry หรือแสดง fallback |
| rate limit |
429 พร้อม Retry-After
|
client backoff ตามเวลาที่เหมาะสม |
| response ช้า |
200 หลัง delay 5 วินาที |
client timeout และ recover ได้ |
| JSON เสีย |
200 พร้อม malformed JSON |
client fail อย่างควบคุมได้ |
ตัวอย่าง test สำหรับ 404:
test('throws clear error when order is not found', async () => {
await expect(getOrder('order_404', BASE_URL))
.rejects
.toThrow('Order not found');
});
ตัวอย่าง test สำหรับ retry เมื่อเจอ 500 อาจเขียน client ให้รับ retry policy แล้ว assert จำนวนครั้งที่เรียก:
test('retries when server returns 500', async () => {
const order = await getOrderWithRetry('order_retry', BASE_URL, {
retries: 2
});
expect(order.id).toBe('order_retry');
});
กฎ mock ขั้นสูงของ Apidog ช่วยให้ส่ง response ต่างกันตาม request ได้ เช่น:
-
GET /orders/order_404ส่ง404 -
GET /orders/order_rate_limitedส่ง429 -
GET /orders/order_8842ส่ง200
วิธีนี้ทำให้ mock endpoint เดียวครอบคลุมทั้ง happy path และ error path ได้ เมื่อใช้ร่วมกับ การยืนยัน API ที่ดี test จะตรวจพฤติกรรมจริง ไม่ใช่แค่ status code
การจัดระเบียบ Mock ใน Test Suite ที่โตขึ้น
mock endpoint เดียวจัดการง่าย แต่เมื่อมีหลายสิบหรือหลายร้อยตัว ต้องมีโครงสร้างที่ชัดเจน
แนวทางที่ควรใช้:
- จัดกลุ่ม mock ตาม service จริง เช่น
orders,payments,users - ตั้งชื่อ fixture ตาม scenario เช่น
order-shipped,order-not-found,order-rate-limited - เก็บ mock definition ไว้ใน version control คู่กับ test
- ใช้ base fixture แล้ว override เฉพาะ field ที่ test ต้องการ
- หลีกเลี่ยงการสร้าง payload เฉพาะใหม่สำหรับทุก test
ตัวอย่างโครงสร้างไฟล์:
tests/
fixtures/
orders/
order-shipped.json
order-not-found.json
order-rate-limited.json
payments/
payment-success.json
payment-failed.json
orderClient.test.js
ตัวอย่าง base fixture:
{
"id": "order_8842",
"status": "shipped",
"total": 149.99,
"items": [
{
"sku": "sku_001",
"name": "Keyboard",
"quantity": 1
}
]
}
หาก test ต้องการเปลี่ยนเฉพาะ status ให้ override เฉพาะ field นั้น แทนการ copy ทั้ง payload ไปทุกไฟล์ วิธีนี้ช่วยลดปัญหา contract เปลี่ยนแล้วต้องแก้หลายจุด
แนวคิดเดียวกับการจัด ชุดทดสอบสำหรับการทำงานอัตโนมัติของ API สามารถใช้กับ mock ได้โดยตรง
รักษาความถูกต้องของ Mock
ปัญหาที่พบบ่อยที่สุดคือ mock drift: backend เปลี่ยน response แล้ว mock ยังส่งรูปแบบเก่า เช่น backend เปลี่ยนจาก:
{
"total": 149.99
}
เป็น:
{
"amount": 149.99
}
แต่ mock ยังใช้ total อยู่ ผลคือ test ผ่าน แต่ production พัง
ลดความเสี่ยงนี้ด้วย 3 วิธี:
-
สร้าง mock จาก schema เดียวกับ backend
- ถ้าใช้ OpenAPI ให้ mock server อ่านจากไฟล์เดียวกัน
- เมื่อ schema เปลี่ยน mock จะเปลี่ยนตาม
-
รัน contract test กับ API จริง
- ไม่ต้องเยอะ
- ให้มี job ที่ยืนยันว่า response จริงยังตรงกับ schema
-
review mock ใน pull request
- ถ้า PR เปลี่ยน API response ต้องเปลี่ยน schema และ mock ที่เกี่ยวข้องด้วย
ตัวอย่าง workflow ใน CI:
# 1. start mock server from OpenAPI
prism mock openapi.yaml &
# 2. run app/client tests against mock
API_BASE_URL=http://127.0.0.1:4010 npm test
# 3. optionally run contract tests against real staging API
API_BASE_URL=https://staging.example.com npm run test:contract
หากต้องการสภาพแวดล้อมเดียวที่เก็บ schema, สร้าง mock และรัน test กับ mock ได้ในที่เดียว คุณสามารถ ดาวน์โหลด Apidog เพื่อจัดการ API design, mock server และ test suite ให้สอดคล้องกัน หรือดูตัวเลือกเพิ่มเติมใน เครื่องมือจำลอง REST API
Checklist สำหรับเริ่มใช้ API Mocking
ใช้รายการนี้เป็นขั้นตอนเริ่มต้น:
- [ ] มี OpenAPI schema หรือ schema เฉพาะ endpoint ที่จะ test
- [ ] มี mock response สำหรับ happy path
- [ ] มี mock response สำหรับ
404,429,500, timeout - [ ] test ใช้
API_BASE_URLแทนการ hardcode URL - [ ] mock definition อยู่ใน version control
- [ ] มี contract test จำนวนเล็กน้อยที่เรียก API จริง
- [ ] CI รัน test กับ mock server ได้อัตโนมัติ
คำถามที่พบบ่อย
ฉันควรจำลอง API สำหรับทุกการทดสอบหรือไม่?
ไม่ควร ใช้ mock สำหรับ unit test และ integration test ที่ต้องการตรวจโค้ดของคุณเอง แต่ควรมี contract test และ end-to-end test จำนวนเล็กน้อยที่เรียก API จริง เพื่อยืนยันว่า mock ยังตรงกับ production contract
Static response กับ dynamic response ต่างกันอย่างไร?
Static response คือ JSON ที่กำหนดตายตัว เหมาะกับ test ที่ต้องการ assert ค่าแน่นอน ส่วน dynamic response ถูกสร้างตาม request ด้วยค่าที่สมจริงกว่า ช่วยเจอ edge case ที่ static payload อาจไม่ครอบคลุม ทีมส่วนใหญ่ใช้ทั้งสองแบบร่วมกัน
จะทำให้ mock แม่นยำอยู่เสมอได้อย่างไร?
สร้าง mock จาก schema เดียวกับ backend เช่น OpenAPI และรัน contract test กับ API จริงเป็นระยะ หาก contract test ล้มเหลว แปลว่า schema หรือ mock อาจล้าสมัยและต้องอัปเดต
Mock server จำลอง response ช้าหรือล้มเหลวได้หรือไม่?
ได้ และเป็นหนึ่งในเหตุผลหลักที่ควรใช้ mock คุณสามารถกำหนดให้ mock ส่ง 500, 429 พร้อม header Retry-After, response delay หรือ malformed JSON เพื่อทดสอบ retry, timeout และ error handling ได้
ควรใช้ local mock server หรือ cloud mock server สำหรับการทดสอบ?
ใช้ local mock server สำหรับ automated test เพราะเร็ว ไม่มี network latency และไม่มี shared state ระหว่าง build ใช้ cloud mock server เมื่อ mobile device, CI runner หรือ collaborator ภายนอกต้องเข้าถึง mock API โดยไม่ต้องรัน server บนเครื่องของคุณเอง
Top comments (0)