การทดสอบที่พึ่งพา API จริงเป็นการทดสอบที่ล้มเหลวด้วยเหตุผลที่ไม่ถูกต้อง เซิร์ฟเวอร์ staging ล่ม, อัตราจำกัดของบุคคลที่สามทำงาน, เพื่อนร่วมทีมเปลี่ยนข้อมูล, และชุดทดสอบของคุณก็กลายเป็นสีแดงในทันที ทั้งๆ ที่โค้ดของคุณยังปกติดี การจำลอง API (Mocking API) ช่วยลดความเปราะบางนั้น คุณจะแทนที่ endpoint จริงด้วยตัวแทนที่ควบคุมได้ ซึ่งจะส่งคืนการตอบกลับที่คุณต้องการทุกครั้ง
คู่มือนี้จะอธิบายขั้นตอนจริงในการจำลอง API สำหรับการทดสอบ คุณจะได้กำหนดสคีมา, สร้างการตอบกลับจำลอง, ชี้โค้ดทดสอบของคุณไปยังตัวจำลอง, และครอบคลุมเส้นทางข้อผิดพลาดที่เซิร์ฟเวอร์จริงไม่ค่อยสร้างขึ้นตามต้องการ ตัวอย่างจะใช้ API จัดการคำสั่งซื้อขนาดเล็ก แต่ขั้นตอนการทำงานนี้สามารถใช้ได้กับบริการ REST หรือ GraphQL ทุกประเภท
เมื่อการจำลอง API เป็นทางเลือกที่ถูกต้อง
จำลอง API เมื่อสิ่งที่คุณต้องการทดสอบคือโค้ดของคุณเอง ไม่ใช่เครือข่าย การทดสอบหน่วย (Unit tests) และการทดสอบบูรณาการ (Integration tests) ส่วนใหญ่จัดอยู่ในหมวดหมู่นี้ คุณต้องการทราบว่าไคลเอ็นต์ของคุณแยกวิเคราะห์ 200 ได้อย่างถูกต้อง, ลองใหม่เมื่อเกิด 503, และแสดงข้อความที่ชัดเจนเมื่อเกิด 404 ซึ่งทั้งหมดนี้ไม่จำเป็นต้องใช้เซิร์ฟเวอร์จริง
เก็บ API จริงไว้สำหรับการทดสอบสัญญา (contract tests) และการตรวจสอบแบบ end-to-end เพียงเล็กน้อย สิ่งเหล่านี้มีอยู่เพื่อยืนยันว่าตัวจำลองยังคงตรงกับความเป็นจริง หากคุณจำลองทุกอย่างและไม่เคยตรวจสอบกับบริการจริง ชุดทดสอบของคุณก็จะยังคงเป็นสีเขียวในขณะที่ระบบโปรดักชันล้ม การแบ่งแยกคร่าวๆ คือ: จำลองเพื่อความเร็วและการแยกส่วน, ใช้ของจริงเพื่อยืนยันสัญญา หากต้องการเจาะลึกว่าแต่ละส่วนเหมาะสมกับที่ใด โปรดดูรายละเอียดของ สถานการณ์ที่การจำลอง API ได้ผล และความแตกต่างระหว่าง เซิร์ฟเวอร์จำลองกับเซิร์ฟเวอร์จริง
ภาพรวมของขั้นตอนการทำงาน 5 ขั้นตอน
การจำลอง API สำหรับการทดสอบมี 5 ขั้นตอนเหมือนกันทุกครั้ง ไม่ว่าจะใช้ภาษาหรือเฟรมเวิร์กใด:
- กำหนดสคีมา เพื่อให้ตัวจำลองสะท้อนรูปแบบการตอบกลับจริง
- สร้างการตอบกลับจำลอง แบบคงที่หรือแบบไดนามิก จากสคีมานั้น
- รันเซิร์ฟเวอร์จำลอง ที่ให้บริการการตอบกลับเหล่านั้นที่ URL
- ชี้การทดสอบของคุณไปยังตัวจำลอง โดยทำให้ base URL สามารถกำหนดค่าได้
- ทดสอบเส้นทางข้อผิดพลาด ที่เซิร์ฟเวอร์จริงจะไม่สร้างขึ้นตามต้องการ
ส่วนที่เหลือของคู่มือนี้จะอธิบายแต่ละขั้นตอนพร้อมตัวอย่างที่เป็นรูปธรรม: API จัดการคำสั่งซื้อขนาดเล็กที่มี endpoint GET /orders/{id} โปรดจำ endpoint นี้ไว้เป็นแนวทางตลอด
ขั้นตอนที่ 1: กำหนดสคีมา
ตัวจำลองจะมีประโยชน์ก็ต่อเมื่อมันสะท้อนรูปแบบการตอบกลับจริง เริ่มจากสคีมา หาก API ของคุณมีเอกสาร OpenAPI อยู่แล้ว ให้ใช้เอกสารนั้น ถ้าไม่มี ให้เขียนขึ้นมาสำหรับ endpoint ที่กำลังทดสอบ นี่คือคำจำกัดความที่กระชับสำหรับ 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-first คือสิ่งที่ทำให้ การทดสอบสัญญา API มีความซื่อตรง
ขั้นตอนที่ 2: สร้างการตอบกลับจำลอง
คุณมีสองวิธีในการสร้างเนื้อหาการตอบกลับ
การตอบกลับแบบคงที่ (Static responses) คือ JSON แบบตายตัว คุณเขียน payload ที่แน่นอนเพียงครั้งเดียว และตัวจำลองจะส่งคืนโดยไม่เปลี่ยนแปลง ซึ่งคาดเดาได้ง่ายและตรวจสอบได้ง่าย ทำให้เหมาะสำหรับการทดสอบหน่วยที่คุณต้องการค่าที่รู้จักเพียงค่าเดียว
การตอบกลับแบบไดนามิก (Dynamic responses) ถูกสร้างขึ้นตามคำขอ แทนที่จะ hardcode "total": 149.99 ตัวจำลองจะเติมฟิลด์ด้วยค่าที่สร้างขึ้นอย่างสมจริง: UUID สำหรับ id, สมาชิก enum แบบสุ่มสำหรับ status, จำนวนเงินที่น่าจะเป็นไปได้สำหรับ total ข้อมูลไดนามิกช่วยตรวจจับข้อผิดพลาดที่ payload แบบตายตัวเพียงอย่างเดียวซ่อนไว้ เช่น parser ที่ล้มเหลวเมื่อเจอสตริงยาวๆ หรือฟิลด์ที่เป็น null โดยไม่คาดคิด
ทีมส่วนใหญ่ใช้ทั้งสองแบบ: payload แบบคงที่สำหรับการทดสอบที่เน้นการยืนยัน (assertion-heavy tests), การสร้างแบบไดนามิกสำหรับการครอบคลุมแบบ fuzz-style ด้วย Apidog คุณไม่จำเป็นต้องเขียนด้วยมือ Apidog จะอ่านสคีมาและสร้าง mock endpoint โดยอัตโนมัติ โดยจับคู่ชื่อฟิลด์เช่น email, phone หรือ avatar กับประเภทข้อมูลที่ถูกต้อง คุณเพียงแค่ชี้เบราว์เซอร์ไปยัง mock URL และจะได้รับการตอบกลับที่ถูกต้องทันที
เมื่อคุณเขียน payload ด้วยมือ ให้พยายามให้สมจริง คำสั่งทดสอบที่มี "total": 0 และอาร์เรย์ items ว่างเปล่าอาจผ่าน parser ที่ไม่ซับซ้อน แต่ก็ซ่อนข้อบกพร่องไว้ได้ ใช้ค่าที่คล้ายคลึงกับการผลิตจริง: ยอดรวมที่ดูสมจริง, รายการสินค้าสองหรือสามรายการ, สถานะที่อยู่ใน enum จริง ยิ่งข้อมูลจำลองใกล้เคียงกับสิ่งที่คำขอจริงส่งคืนมากเท่าไหร่ การทดสอบของคุณก็ยิ่งมีคุณค่ามากขึ้นเท่านั้น
ขั้นตอนที่ 3: รันเซิร์ฟเวอร์จำลอง
การตอบกลับจำลองจะไม่มีประโยชน์จนกว่าจะมีบางสิ่งบางอย่างให้บริการ คุณมีสองทางเลือกในการโฮสต์
เซิร์ฟเวอร์จำลองแบบโลคัล (Local mock server) ทำงานบนเครื่องของคุณ โดยปกติจะอยู่บนพอร์ตเช่น localhost:4010 มันเร็ว ทำงานแบบออฟไลน์ได้ และเป็นค่าเริ่มต้นสำหรับการทดสอบหน่วยและการทดสอบบูรณาการ เครื่องมือเบาๆ อย่าง Prism สามารถรันขึ้นได้โดยตรงจากไฟล์ OpenAPI:
prism mock openapi.yaml
# Mock server listening on http://127.0.0.1:4010
เซิร์ฟเวอร์จำลองบนคลาวด์ (Cloud mock server) มี URL สาธารณะ ให้เลือกใช้เมื่อแอปพลิเคชันมือถือ, CI runner, หรือผู้ทำงานร่วมกันภายนอกจำเป็นต้องเรียกใช้ตัวจำลองโดยไม่ต้องเข้าถึงแล็ปท็อปของคุณ Apidog ให้ Cloud Mock URL ที่โฮสต์สำหรับทุกโปรเจกต์ ดังนั้นเพื่อนร่วมทีมในเครือข่ายอื่นสามารถเรียกใช้ endpoint เดียวกับคุณได้
สำหรับการรันทดสอบ ควรเลือกใช้แบบโลคัล มันไม่มีความล่าช้าของเครือข่ายและไม่มีสถานะที่ใช้ร่วมกัน ดังนั้นสอง build จะไม่ชนกัน ใช้ตัวเลือกคลาวด์สำหรับการสาธิตและการทดสอบข้ามอุปกรณ์
ขั้นตอนที่ 4: ชี้การทดสอบของคุณไปยังตัวจำลอง
ตอนนี้ให้เชื่อมต่อโค้ดทดสอบของคุณกับตัวจำลองแทนที่จะเป็นระบบโปรดักชัน วิธีที่สะอาดที่สุดคือการใช้ base URL ที่สามารถกำหนดค่าได้ อ่านจากตัวแปรสภาพแวดล้อมเพื่อให้ไฟล์ทดสอบเดียวกันทำงานกับตัวจำลองในเครื่อง และ API จริงในงาน contract job
// 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');
});
ไคลเอ็นต์รับ base URL เป็นอาร์กิวเมนต์; ไม่มีส่วนใดในโค้ดโปรดักชันที่รู้ว่ากำลังถูกจำลอง ใน CI คุณตั้งค่า API_BASE_URL เป็นที่อยู่ของตัวจำลองก่อนที่ชุดทดสอบจะทำงาน รูปแบบนี้ช่วยให้การจำลองอยู่นอกตรรกะของแอปพลิเคชันของคุณอย่างสมบูรณ์ ซึ่งเป็นที่ที่มันควรอยู่ หากคุณรันการทดสอบผ่าน pipeline แนวคิดเดียวกันนี้ก็ใช้ได้เมื่อคุณ ทำให้การทดสอบ API เป็นอัตโนมัติใน CI/CD
ขั้นตอนที่ 5: ทดสอบเส้นทางข้อผิดพลาด
นี่คือขั้นตอนที่ทีมส่วนใหญ่มักข้ามไป และเป็นขั้นตอนที่ให้ผลตอบแทนสูง เซิร์ฟเวอร์จริงไม่ค่อยส่งคืน 500 เมื่อคุณต้องการ แต่ตัวจำลองจะส่งคืนเมื่อคุณสั่ง
กำหนดค่าตัวจำลองให้ส่งคืนการตอบกลับที่ล้มเหลวเฉพาะเจาะจง จากนั้นยืนยันว่าไคลเอ็นต์ของคุณจัดการแต่ละกรณีได้:
| สถานการณ์ | ตัวจำลองส่งคืน | สิ่งที่คุณยืนยัน |
|---|---|---|
| ไม่มีข้อมูลบันทึก | 404 |
ไคลเอ็นต์ส่งข้อผิดพลาด "ไม่พบ" ที่ชัดเจน |
| เซิร์ฟเวอร์ล้มเหลว | 500 |
ไคลเอ็นต์ลองใหม่ แล้วแสดงผลสำรอง |
| ถูกจำกัดอัตรา | 429 พร้อม Retry-After |
ไคลเอ็นต์ถอยกลับตามเวลาที่เหมาะสม |
| การตอบกลับช้า | 200 หลังจากหน่วงเวลา 5 วินาที |
ไคลเอ็นต์หมดเวลาและกู้คืน |
| เนื้อหาผิดรูปแบบ | 200 พร้อม JSON ที่เสียหาย |
ไคลเอ็นต์ล้มเหลวอย่างสง่างาม ไม่เกิดข้อผิดพลาด |
กฎการจำลองขั้นสูงของ Apidog ช่วยให้คุณสามารถส่งคืนการตอบกลับที่แตกต่างกันตามคำขอ ดังนั้นคำขอสำหรับ order_404 จะให้ 404 ในขณะที่ ID อื่นๆ ทั้งหมดจะส่งคืน 200 ปกติ นั่นทำให้คุณมี mock endpoint เดียวที่ครอบคลุมทั้งกรณีปกติและกรณีข้อผิดพลาด ควบคู่ไปกับการ ยืนยัน API ที่แข็งแกร่ง ชุดทดสอบของคุณจะตรวจสอบพฤติกรรม ไม่ใช่แค่รหัสสถานะ
การจัดระเบียบตัวจำลองในชุดทดสอบที่กำลังเติบโต
mock endpoint เดียวเป็นเรื่องง่าย แต่การมีร้อยตัวกระจายอยู่ในชุดทดสอบอาจทำให้ทีมควบคุมไม่ได้ มีพฤติกรรมบางอย่างที่ช่วยให้ชุดนี้จัดการได้ง่าย
จัดกลุ่มตัวจำลองตามบริการจริงที่พวกมันเป็นตัวแทน ไม่ใช่ตามการทดสอบที่ใช้ เมื่อ API การชำระเงินเปลี่ยนแปลง คุณต้องการอัปเดตเพียงที่เดียว ไม่ใช่ยี่สิบไฟล์ทดสอบ ตั้งชื่อ mock fixtures ตามสถานการณ์ที่พวกมันเป็นตัวแทน เช่น order-shipped หรือ order-rate-limited เพื่อให้การทดสอบที่ล้มเหลวอ่านได้ชัดเจน เก็บคำจำกัดความของตัวจำลองไว้ในระบบควบคุมเวอร์ชันถัดจากการทดสอบ เนื่องจากตัวจำลองเป็นส่วนหนึ่งของการทดสอบและสมควรได้รับการตรวจสอบเช่นเดียวกัน
หลีกเลี่ยงการให้ทุกการทดสอบมี payload เฉพาะของตัวเอง การทดสอบส่วนใหญ่ต้องการวัตถุคำสั่งซื้อที่สมจริงเดียวกันโดยมีการเปลี่ยนแปลงเพียงฟิลด์เดียว กำหนดการตอบกลับพื้นฐานเพียงครั้งเดียวและแทนที่เฉพาะฟิลด์ที่กำลังทดสอบเท่านั้น ซึ่งจะทำให้ชุดทดสอบอ่านง่ายและหมายความว่าการเปลี่ยนแปลงสัญญาจะส่งผลกระทบต่อคำจำกัดความพื้นฐานเดียว แทนที่จะเป็นสำเนาที่กระจัดกระจาย วินัยแบบเดียวกันที่ทำให้ ชุดทดสอบสำหรับการทำงานอัตโนมัติของ API สามารถบำรุงรักษาได้ ก็สามารถนำมาใช้โดยตรงกับตัวจำลองที่อยู่เบื้องหลังได้
รักษาความซื่อสัตย์ของตัวจำลอง
ตัวจำลองอาจคลาดเคลื่อน แบ็คเอนด์อาจเพิ่มฟิลด์ เปลี่ยนชื่อ total เป็น amount หรือเปลี่ยน enum และตัวจำลองของคุณก็ยังคงส่งคืนรูปแบบเก่า การทดสอบผ่าน แต่ระบบโปรดักชันล้มเหลว นี่เป็นวิธีที่พบบ่อยที่สุดที่การจำลองผิดพลาด และมันเป็นไปอย่างเงียบๆ ไม่มีอะไรในชุดทดสอบของคุณบ่น เพราะชุดทดสอบกำลังวัดตัวจำลองกับตัวมันเอง
มีสองนิสัยที่ช่วยป้องกันเรื่องนี้ อย่างแรก สร้างตัวจำลองจากสคีมาเดียวกันกับที่แบ็คเอนด์เผยแพร่ ดังนั้นการเปลี่ยนแปลงสัญญาจะอัปเดตทั้งสองพร้อมกัน ตัวจำลองที่สร้างจากไฟล์ OpenAPI จะสร้างใหม่เมื่อไฟล์นั้นเปลี่ยนแปลง แต่ตัวจำลองที่พิมพ์ด้วยมือจะไม่เป็นเช่นนั้น อย่างที่สอง รันชุดการทดสอบสัญญาจำนวนเล็กน้อยกับ API จริงตามกำหนดเวลา งานเดียวของพวกมันคือการยืนยันว่าการตอบกลับจริงยังคงตรงกับสคีมาที่ตัวจำลองของคุณใช้ เมื่อพวกมันล้มเหลว คุณจะรู้ว่าตัวจำลองนั้นล้าสมัยก่อนที่ผู้ใช้จะรู้
การตรวจสอบตัวจำลองในระหว่างการรีวิวโค้ดก็ช่วยได้เช่นกัน เมื่อ pull request เปลี่ยนการตอบกลับของ API ผู้รีวิวควรตรวจสอบว่าตัวจำลองที่เกี่ยวข้องมีการเปลี่ยนแปลงด้วยหรือไม่ การปฏิบัติต่อตัวจำลองเป็นส่วนหนึ่งของสัญญา แทนที่จะเป็นเพียงตัวช่วยทดสอบที่ใช้แล้วทิ้ง คือสิ่งที่ทำให้ชุดทดสอบที่ใช้ตัวจำลองน่าเชื่อถือตลอดการเปลี่ยนแปลงหลายเดือน
หากคุณต้องการสภาพแวดล้อมเดียวที่เก็บสคีมา สร้างตัวจำลอง และรันการทดสอบกับมัน ดาวน์โหลด Apidog มันจะทำให้การออกแบบ, เซิร์ฟเวอร์จำลอง และชุดทดสอบทำงานสอดคล้องกัน ดังนั้นตัวจำลองที่คุณใช้ทดสอบจะยังคงเป็นสัญญาปัจจุบันเสมอ สำหรับตัวเลือกที่หลากหลายมากขึ้น โปรดเปรียบเทียบในสรุป เครื่องมือจำลอง REST API นี้
คำถามที่พบบ่อย
ฉันควรจำลอง API สำหรับทุกการทดสอบหรือไม่?
ไม่ ควรจำลอง API สำหรับการทดสอบหน่วยและการทดสอบบูรณาการที่คุณกำลังตรวจสอบโค้ดของคุณเอง เก็บชุดการทดสอบสัญญาและ end-to-end จำนวนเล็กน้อยที่เรียกใช้ API จริง เนื่องจากสิ่งเหล่านี้ยืนยันว่าตัวจำลองของคุณยังคงตรงกับระบบโปรดักชัน การจำลองทุกอย่างจะซ่อนการคลาดเคลื่อนของสัญญา
ความแตกต่างระหว่างการตอบกลับจำลองแบบคงที่และแบบไดนามิกคืออะไร?
การตอบกลับแบบคงที่คือ payload JSON ที่ตายตัวซึ่งไม่เปลี่ยนแปลง ซึ่งดีสำหรับการยืนยันที่คาดเดาได้ การตอบกลับแบบไดนามิกถูกสร้างขึ้นตามคำขอพร้อมค่าที่สมจริง ซึ่งช่วยตรวจจับข้อผิดพลาดที่ payload แบบตายตัวเพียงอย่างเดียวอาจพลาดไป ทีมส่วนใหญ่ใช้ทั้งสองแบบ
ฉันจะแน่ใจได้อย่างไรว่าตัวจำลองของฉันยังคงแม่นยำ?
สร้างตัวจำลองจากสคีมาเดียวกันกับที่แบ็คเอนด์ใช้ ซึ่งโดยหลักการแล้วคือเอกสาร OpenAPI จากนั้นรันการทดสอบสัญญาตามกำหนดเวลากับ API จริงเพื่อยืนยันว่าการตอบกลับจริงยังคงตรงกับสคีมานั้น หากล้มเหลว ตัวจำลองของคุณจำเป็นต้องได้รับการอัปเดต
ตัวจำลองสามารถจำลองการตอบกลับที่ช้าหรือล้มเหลวได้หรือไม่?
ได้ และนี่คือหนึ่งในเหตุผลที่แข็งแกร่งที่สุดในการจำลอง คุณสามารถกำหนดค่าตัวจำลองให้ส่งคืน 500, 429 พร้อมเฮดเดอร์ Retry-After หรือ 200 ที่มีการหน่วงเวลาได้ นั่นช่วยให้คุณสามารถตรวจสอบตรรกะการลองใหม่และการหมดเวลาที่เซิร์ฟเวอร์จริงที่ทำงานปกติจะไม่มีทางสร้างขึ้นตามต้องการ
เซิร์ฟเวอร์จำลองแบบโลคัลหรือเซิร์ฟเวอร์จำลองบนคลาวด์สำหรับการทดสอบ?
ใช้เซิร์ฟเวอร์จำลองแบบโลคัลสำหรับการรันทดสอบ มันเร็ว ไม่มี latency ของเครือข่าย และหลีกเลี่ยงสถานะที่ใช้ร่วมกันระหว่าง build ใช้ตัวจำลองที่โฮสต์บนคลาวด์เมื่ออุปกรณ์มือถือ, CI runner, หรือผู้ทำงานร่วมกันภายนอกต้องการเข้าถึงตัวจำลองโดยไม่ต้องเข้าถึงเครื่องของคุณ
