หากการทดสอบ Node.js ของคุณล้มเหลวเพราะ API ของบุคคลที่สามไม่ทำงาน ทำงานช้า หรือถูกจำกัดอัตรา (rate-limited) ปัญหาอาจไม่ได้อยู่ที่โค้ดของคุณ แต่อยู่ที่ dependency ภายนอก วิธีแก้คือ mock เลเยอร์ HTTP เพื่อให้ unit tests ทำงานได้เร็วและให้ผลลัพธ์เหมือนเดิมทุกครั้ง ใน Node.js เครื่องมือที่นิยมใช้คือ nock ซึ่งช่วยดักจับ HTTP request ภายใน process และส่ง response ที่กำหนดไว้กลับมาแทน API จริง คู่มือนี้จะแสดงวิธีติดตั้ง ใช้งาน ทดสอบ error case และอธิบายว่าเมื่อไรควรใช้ เซิร์ฟเวอร์จำลอง (mock server) ที่แชร์ร่วมกัน แทนการ mock ใน test process สำหรับรายละเอียด API ทั้งหมด โปรดดู ที่เก็บ nock บน GitHub
nock คืออะไร?
nock คือไลบรารีสำหรับ mock HTTP server และกำหนด expectation ใน Node.js มันทำงานโดย override โมดูล http และ https ของ Node ตอน runtime ดังนั้น request ภายนอกที่โค้ดของคุณส่งออกไปจะถูกดักจับและตอบกลับด้วย response ที่เตรียมไว้ โดยไม่ต้องเรียก network จริง
ประโยชน์หลักในการทำ unit test คือคุณสามารถแยก logic ของแอปออกจาก dependency ภายนอกได้ เช่น API จริงอาจช้า มีค่าใช้จ่าย ล่ม หรือส่งผลลัพธ์ไม่แน่นอน แต่ด้วย nock คุณสามารถกำหนดได้ว่า:
เมื่อโค้ดส่ง
GETไปยัง URL นี้ ให้ตอบกลับ JSON นี้ พร้อม status code นี้
จากนั้น test จะตรวจสอบเฉพาะว่าโค้ดของคุณจัดการ response นั้นถูกต้องหรือไม่
nock ใช้ได้กับ Node.js เท่านั้น เพราะมันผูกกับ HTTP stack ของ Node จึงทำงานร่วมกับ client ที่สร้างบน http/https ได้ เช่น fetch, axios, got และ node-fetch โดยทั่วไปควรติดตั้งเป็น dev dependency เพราะใช้เฉพาะใน test ไม่ใช่ production
ติดตั้ง nock
ติดตั้งด้วย npm:
npm install --save-dev nock
หรือใช้ pnpm/yarn:
pnpm add -D nock
yarn add -D nock
ตัวอย่าง nock แบบใช้งานจริง
สมมติว่าคุณมีฟังก์ชันสำหรับดึงข้อมูลผู้ใช้จาก API:
// user-service.js
export async function getUser(id) {
const res = await fetch(`https://api.example.com/users/${id}`);
if (!res.ok) {
throw new Error(`Request failed: ${res.status}`);
}
return res.json();
}
เขียน test ด้วย nock ได้แบบนี้:
// user-service.test.js
import nock from 'nock';
import { getUser } from './user-service.js';
test('returns the user when the API responds', async () => {
const scope = nock('https://api.example.com')
.get('/users/42')
.reply(200, { id: 42, name: 'Ada Lovelace' });
const user = await getUser(42);
expect(user).toEqual({ id: 42, name: 'Ada Lovelace' });
scope.done();
});
โค้ดนี้ทำงานตามลำดับดังนี้:
-
nock('https://api.example.com')กำหนด host ที่ต้องการดักจับ -
.get('/users/42')ระบุ HTTP method และ path -
.reply(200, {...})ระบุ status code และ response body -
getUser(42)ทำงานตามปกติ แต่ request จริงจะไม่ถูกส่งออกไป -
scope.done()ตรวจสอบว่า request ที่คาดไว้ถูกเรียกจริง
ทดสอบ error case ด้วย nock
อย่าทดสอบเฉพาะ happy path ให้ mock error response ด้วย เพราะนี่คือจุดที่ API จริงมักควบคุมได้ยาก
test('throws when the API returns 500', async () => {
const scope = nock('https://api.example.com')
.get('/users/99')
.reply(500);
await expect(getUser(99)).rejects.toThrow('Request failed: 500');
scope.done();
});
การทดสอบเส้นทางที่ไม่สำเร็จ (unhappy path) มีประโยชน์มาก เพราะคุณไม่สามารถบังคับให้ API จริงส่ง 500 Internal Server Error ได้อย่างน่าเชื่อถือ แต่ nock ทำได้ในบรรทัดเดียว หากต้องการเจาะลึกเรื่องนี้ อ่านเพิ่มเติมได้ที่บทความ จำลองการตอบสนองข้อผิดพลาด 500 Internal Server Error
คุณสมบัติ nock ที่ควรรู้
เมื่อเริ่มใช้ nock ใน test suite จริง เมธอดเหล่านี้จะใช้บ่อยมาก
ใช้ .persist() เมื่อ endpoint ถูกเรียกหลายครั้ง
โดยค่าเริ่มต้น interceptor ของ nock จะถูกใช้ได้เพียงครั้งเดียว ถ้า endpoint เดียวกันถูกเรียกหลายครั้ง ให้ใช้ .persist()
nock('https://api.example.com')
.persist()
.get('/health')
.reply(200, { status: 'ok' });
ใช้ .times(n) เพื่อจำกัดจำนวนครั้ง
ถ้าต้องการให้ mock ใช้งานได้ตามจำนวนครั้งที่กำหนด:
nock('https://api.example.com')
.get('/users/42')
.times(2)
.reply(200, { id: 42, name: 'Ada Lovelace' });
ใช้ .delay(ms) เพื่อทดสอบ timeout
nock('https://api.example.com')
.get('/slow-endpoint')
.delay(2000)
.reply(200, { ok: true });
เหมาะสำหรับทดสอบ logic ที่เกี่ยวกับ timeout, retry หรือ fallback
ใช้ RegExp เมื่อ path หรือ query เปลี่ยนได้
nock('https://api.example.com')
.get(/\/users\/\d+/)
.reply(200, { id: 123, name: 'Mock User' });
เหมาะเมื่อ ID, query string หรือ path บางส่วนเป็น dynamic
ล้าง mock ระหว่าง test
เพื่อป้องกัน mock จาก test หนึ่งรั่วไปอีก test หนึ่ง ให้ล้าง interceptor หลังแต่ละ test:
import nock from 'nock';
afterEach(() => {
nock.cleanAll();
});
ตรวจสอบว่า mock ถูกใช้จริง
ควรเรียก scope.done() หรือใช้ nock.isDone() เพื่อให้ test fail เมื่อ request ที่คาดไว้ไม่เกิดขึ้น
const scope = nock('https://api.example.com')
.get('/users/42')
.reply(200, { id: 42 });
await getUser(42);
scope.done();
ถ้าไม่มีการเรียก /users/42 test จะล้มเหลวทันที ซึ่งช่วยจับ bug ได้ดีกว่าการปล่อยให้ mock ที่ไม่ได้ใช้ผ่านไปเงียบๆ
เมื่อ nock ไม่ใช่เครื่องมือที่เหมาะสมอีกต่อไป
nock เหมาะกับงานเฉพาะด้านมาก: ดักจับ HTTP ภายใน Node process ระหว่าง automated tests
แต่เมื่อ requirement ข้าม process boundary ข้อจำกัดจะเริ่มชัดเจน:
- mock ของ nock อยู่ในไฟล์ test
- mock มีผลเฉพาะ runtime ของ test นั้น
- browser เรียกใช้ nock โดยตรงไม่ได้
- mobile simulator เรียก mock นั้นไม่ได้
- QA ใช้ทดสอบ manual จาก URL จริงไม่ได้
- Postman collection เรียก mock ที่อยู่ใน Jest/Mocha process ไม่ได้
ดังนั้น nock เหมาะกับ unit tests แต่ไม่เหมาะเมื่อหลายทีมต้องใช้ mock เดียวกันผ่าน network จริง
ตัวอย่างสถานการณ์ที่ควรใช้ mock server แทน:
- ทีมฟรอนต์เอนด์ต้องพัฒนา endpoint ก่อนแบ็กเอนด์เสร็จ
- หลาย repo ต้องอ้างอิง fake response ชุดเดียวกัน
- ต้อง demo integration โดยไม่มี backend จริง
- QA ต้องทดสอบ edge cases ด้วยตนเองโดยไม่เขียนโค้ด
ในกรณีเหล่านี้คุณต้องใช้ mock server ซึ่งรับ request จาก URL จริงและส่ง response กลับไปให้ client ใดก็ได้ หากกำลังเปรียบเทียบแนวคิดนี้ อ่านเพิ่มเติมได้ที่ mock server กับ real server และภาพรวมของ การจำลอง API (API mocking)
nock เทียบกับ hosted mock server เช่น Apidog
ให้มอง nock และ hosted mock server เป็นคนละเลเยอร์ ไม่ใช่เครื่องมือที่ต้องเลือกอย่างใดอย่างหนึ่ง
- nock: mock request ภายใน Node test process
- Apidog: สร้าง mock server ที่แชร์ได้และมี URL จริง สำหรับใช้นอก test process
หลายทีมใช้ทั้งสองอย่างร่วมกัน: nock สำหรับ unit tests และ mock server สำหรับ frontend, QA หรือ integration testing
| หัวข้อ | nock | Apidog mock server |
|---|---|---|
| ทำงานที่ไหน | ในกระบวนการทดสอบ Node | เซิร์ฟเวอร์โฮสต์ที่มี URL จริง |
| เหมาะที่สุดสำหรับ | Unit tests, error simulation | Integration, manual testing, cross-team collaboration |
| ใครเข้าถึงได้ | โค้ดใน process เดียวกัน | client ใดๆ ที่มี URL |
| การตั้งค่า | เขียนในแต่ละไฟล์ test | สร้างจาก API schema |
| ภาษา | Node.js เท่านั้น | client ใดๆ, ภาษาใดๆ |
| ข้อมูลที่สมจริง | เขียน response เองทีละรายการ | mock จาก schema และชื่อ field |
| การแบ่งปัน | แชร์โดยตรงไม่ได้ | แชร์ได้ทั้งทีม |
ขอบเขตการใช้งานควรชัดเจน: Apidog ไม่ได้มาแทนที่ nock ใน Jest หรือ Mocha unit tests ถ้าคุณต้องการดักจับ fetch ใน test เดียวและ assert ผลลัพธ์ nock ยังเป็นเครื่องมือที่เหมาะสมกว่า
แต่ถ้า mock ต้องมี address จริงที่คนอื่นหรือเครื่องมืออื่นเรียกได้ Apidog จะเหมาะกว่า เช่น frontend app, mobile app, QA tool หรือ Postman collection สำหรับตัวอย่างเพิ่มเติม ดู คู่มือเชิงปฏิบัติเกี่ยวกับการจำลอง API สำหรับการทดสอบ หรือ ดาวน์โหลด Apidog เพื่อสร้าง mock จาก OpenAPI ที่มีอยู่
ทางเลือกอื่นสำหรับ nock
nock ไม่ใช่ตัวเลือกเดียว การเลือกเครื่องมือขึ้นอยู่กับว่าโค้ดของคุณรันที่ไหนและต้องการ mock ระดับใด
MSW (Mock Service Worker)
MSW ดักจับ request ที่ระดับ network โดยใช้ service worker ใน browser และ Node interceptor บน server จุดเด่นคือสามารถใช้ mock definition เดียวกันได้ทั้ง browser และ Node เหมาะกับทีมที่ทำ full-stack JavaScript
อ่านเพิ่มเติมได้ที่ เอกสารอย่างเป็นทางการของ MSW
Jest manual mocks
ถ้าโค้ดของคุณใช้ client เฉพาะ เช่น axios คุณสามารถ mock module นั้นโดยตรงใน Jest ได้ วิธีนี้ง่ายสำหรับ case เล็กๆ แต่จะผูก test กับ HTTP client ที่เลือกใช้
ตัวอย่างแนวคิดนี้อยู่ในบทความ บทเรียนการจำลอง (mocking) ของ Jest
Built-in test doubles หรือ Sinon
คุณสามารถ stub function ที่ทำ HTTP call โดยตรงด้วย test runner หรือไลบรารีอย่าง Sinon ได้ วิธีนี้เหมาะเมื่อคุณต้องการควบคุม function boundary แต่จะเสียความสามารถในการ match ระดับ HTTP แบบที่ nock ให้มา
คำถามหลักในการเลือกคือ:
คุณกำลังทดสอบ logic ภายใน process เดียว หรือคุณต้องการ API ปลอมที่ client อื่นเรียกผ่าน network ได้?
ถ้าเป็นอย่างแรก ใช้ nock หรือ MSW ถ้าเป็นอย่างหลัง ใช้ hosted mock server
คำถามที่พบบ่อย
nock ฟรีหรือไม่?
ใช่ nock เป็นโอเพนซอร์สภายใต้ใบอนุญาต MIT และติดตั้งได้ฟรีจาก npm โดยทั่วไปจะเพิ่มเป็น dev dependency และใช้ใน test suite ได้ฟรี
nock ทำงานร่วมกับ fetch และ axios ได้หรือไม่?
ใช่ เพราะ nock ดักจับที่เลเยอร์ http/https ของ Node จึงทำงานกับ client ที่สร้างบนโมดูลเหล่านี้ได้ เช่น native fetch, axios, got และ node-fetch
ฉันใช้ nock ในเบราว์เซอร์ได้หรือไม่?
ไม่ได้ nock ใช้ได้กับ Node.js เท่านั้น เพราะมันแก้ไขโมดูล HTTP ของ Node ซึ่งไม่มีใน browser หากต้องการ mock ฝั่ง browser ให้ใช้ MSW หรือชี้ frontend ไปยัง hosted mock server ดูตัวเลือกเพิ่มเติมได้ในภาพรวม mock API ใน JavaScript
nock ต่างจาก mock server อย่างไร?
nock ดักจับ request ภายใน test process และไม่เปิดพอร์ตจริง ส่วน mock server รับ request จาก URL จริงที่ client ใดๆ เรียกได้
ใช้ nock สำหรับ unit tests ใช้ mock server เมื่อ frontend, QA หรือทีมอื่นต้องเข้าถึง fake response ชุดเดียวกัน
สรุป
nock เป็นเครื่องมือที่เชื่อถือได้สำหรับการ mock HTTP ใน Node.js unit tests มันดักจับ outbound request ใน process ส่ง response ที่คุณกำหนด และทำให้ test suite เร็วขึ้น มีเสถียรภาพขึ้น และ deterministic มากขึ้น โดยเฉพาะ error path ที่ควบคุมจาก API จริงได้ยาก
เมื่อ mock ต้องออกจากไฟล์ test เช่น frontend, QA หรือทีมอื่นต้องเรียก endpoint เดียวกันผ่าน URL จริง ให้ใช้ shared mock server แทน Apidog สามารถสร้าง mock server จาก API schema และส่งข้อมูลจำลองผ่าน URL จริง เพื่อให้ทีมทำงานตาม API contract เดียวกันก่อน backend พร้อมใช้งาน คุณสามารถ ดาวน์โหลด Apidog และเปลี่ยน OpenAPI spec ให้เป็น mock ที่ใช้งานได้ในไม่กี่นาที

Top comments (0)