หากการทดสอบ Node.js ของคุณล้มเหลวเนื่องจาก API ของบุคคลที่สามไม่ทำงาน ทำงานช้า หรือถูกจำกัดอัตรา (rate-limited) แสดงว่าคุณไม่ได้มีปัญหาเกี่ยวกับโค้ด คุณมีปัญหาการพึ่งพา (dependency problem) วิธีแก้ไขคือการจำลอง (mock) เลเยอร์ HTTP เพื่อให้การทดสอบหน่วย (unit tests) ของคุณทำงานได้เหมือนเดิมทุกครั้ง และ nock คือแพ็คเกจ npm ที่นักพัฒนา Node ส่วนใหญ่เลือกใช้ คู่มือนี้จะอธิบายว่า nock คืออะไร แสดงตัวอย่างการใช้งานเล็กๆ น้อยๆ และครอบคลุมถึงกรณีที่ เซิร์ฟเวอร์จำลอง (mock server) ที่แชร์ร่วมกัน เหมาะสมกว่าการดักจับในกระบวนการ (in-process interception) สำหรับข้อมูลอ้างอิงไลบรารีฉบับเต็ม ที่เก็บ nock บน GitHub คือแหล่งข้อมูลที่เชื่อถือได้
nock คืออะไร?
nock คือไลบรารีสำหรับจำลองเซิร์ฟเวอร์ HTTP และกำหนดเงื่อนไข (expectations) สำหรับ Node.js มันทำงานโดยการแทนที่ (override) โมดูล http และ https ของ Node ในขณะรันไทม์ (runtime) ทำให้คำขอภายนอกใดๆ ที่โค้ดของคุณส่งออกไปสามารถถูกดักจับและตอบกลับด้วยการตอบสนองที่เตรียมไว้ ไม่มีข้อมูลใดออกจากเครื่องของคุณ การเรียกใช้เครือข่ายจะไม่เกิดขึ้นจริง
สิ่งนี้สำคัญสำหรับการทดสอบหน่วย (unit tests) เมื่อคุณทดสอบฟังก์ชันที่เรียกใช้ API ภายนอก คุณไม่ต้องการเรียกใช้บริการจริง การเรียกใช้จริงนั้นช้า มีค่าใช้จ่าย อาจล้มเหลวด้วยเหตุผลที่ไม่เกี่ยวข้องกับโค้ดของคุณ และทำให้ชุดการทดสอบของคุณไม่สามารถคาดการณ์ผลลัพธ์ที่แน่นอนได้ (non-deterministic) nock ช่วยให้คุณระบุได้ว่า “เมื่อโค้ดของฉันส่งคำขอ GET ไปยัง URL นี้ ให้ส่งกลับ JSON ที่แน่นอนพร้อมรหัสสถานะนี้” จากนั้นการทดสอบของคุณจะยืนยันว่าฟังก์ชันของคุณจัดการกับการตอบสนองนั้นอย่างไร
nock ใช้ได้กับ Node.js เท่านั้น มันเชื่อมต่อเข้ากับ HTTP stack ดั้งเดิม จึงทำงานร่วมกับ fetch, axios, got, node-fetch และสิ่งอื่นๆ ที่สร้างขึ้นบน http/https ได้ มันมีการดาวน์โหลดหลายล้านครั้งต่อสัปดาห์ และเป็นตัวเลือกเริ่มต้นสำหรับการทดสอบแบ็กเอนด์มานานหลายปี คุณติดตั้งมันเป็น dev dependency เพราะมันทำงานเฉพาะในการทดสอบเท่านั้น ไม่ได้ใช้ในการผลิตจริง (production)
ตัวอย่าง 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();
}
และนี่คือการทดสอบที่ใช้ nock เพื่อดักจับคำขอ:
// user-service.test.js
import nock from 'nock';
import { getUser } from './user-service.js';
test('returns the user when the API responds', async () => {
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' });
});
อ่านจากบนลงล่าง nock('https://api.example.com') กำหนดโฮสต์ที่คุณต้องการดักจับ .get('/users/42') ตรงกับเมธอดและพาธ .reply(200, {...}) กำหนดสถานะและเนื้อหาที่จะส่งกลับ เมื่อ getUser(42) ทำงาน การเรียกใช้เครือข่ายจริงจะถูกแทนที่ และการตอบสนองที่คุณเตรียมไว้จะถูกส่งกลับมาแทน
คุณสามารถจำลองข้อผิดพลาดได้ด้วยวิธีเดียวกัน ซึ่งเป็นส่วนที่คนมักจะลืมทดสอบ:
test('throws when the API returns 500', async () => {
nock('https://api.example.com')
.get('/users/99')
.reply(500);
await expect(getUser(99)).rejects.toThrow('Request failed: 500');
});
การทดสอบเส้นทางที่ไม่ประสบความสำเร็จ (unhappy path) คือจุดที่การจำลองมีคุณค่าอย่างยิ่ง คุณไม่สามารถทำให้ API จริงส่งคืนค่า 500 ได้ตามต้องการอย่างน่าเชื่อถือ แต่คุณสามารถสร้างข้อผิดพลาดปลอมขึ้นมาได้ในบรรทัดเดียว หากการจำลองข้อผิดพลาดเป็นเป้าหมายหลักของคุณ บทความนี้เกี่ยวกับวิธี จำลองการตอบสนองข้อผิดพลาด 500 Internal Server Error จะเจาะลึกยิ่งขึ้น
คุณสมบัติ nock ที่มีประโยชน์ที่ควรรู้
มีเมธอดบางตัวที่ถูกใช้งานบ่อยครั้งเมื่อคุณก้าวข้ามพื้นฐานไปแล้ว
.persist()ทำให้ interceptor ยังคงใช้งานได้ต่อไปในการเรียกใช้หลายครั้ง โดยค่าเริ่มต้น nock จะลบ interceptor ออกหลังจากที่ตรงกันเพียงครั้งเดียว ซึ่งเหมาะสำหรับการยืนยันว่าคำขอเกิดขึ้นเพียงครั้งเดียวเท่านั้น ใช้.persist()เมื่อมีการเรียกใช้ endpoint เดียวกันซ้ำๆ ในการทดสอบเดียว.times(n)จะจับคู่คำขอเดียวกันเป็นจำนวนครั้งที่กำหนดก่อนที่ interceptor จะหมดอายุ.delay(ms)จำลองการตอบสนองที่ช้า เพื่อให้คุณสามารถทดสอบการจัดการกับข้อผิดพลาดเมื่อหมดเวลา (timeout handling)- การจับคู่ด้วย RegExp (Regular Expression) ช่วยให้โฮสต์หรือพาธเป็นรูปแบบ (pattern) แทนที่จะเป็นสตริงคงที่ ซึ่งมีประโยชน์เมื่อ ID หรือ query string แตกต่างกัน
nock.cleanAll()ล้าง interceptor ทั้งหมด รันมันระหว่างการทดสอบ เพื่อไม่ให้ mock ของการทดสอบหนึ่งรั่วไหลไปสู่การทดสอบถัดไป
นิสัยหนึ่งที่ควรสร้างขึ้นตั้งแต่เนิ่นๆ คือ: ยืนยันว่า mock ทั้งหมดของคุณถูกใช้งานจริง เรียกใช้ scope.done() บน interceptor (หรือ nock.isDone()) และการทดสอบจะล้มเหลวหากคำขอที่คาดหวังไม่เคยถูกเรียกใช้ สิ่งนี้จะเปลี่ยนการเรียกใช้ที่พลาดไปอย่างเงียบๆ ให้กลายเป็นการล้มเหลวที่ชัดเจน
เมื่อ nock ไม่ใช่เครื่องมือที่เหมาะสมอีกต่อไป
nock ถูกสร้างมาเพื่อวัตถุประสงค์เดียวและทำงานได้ดี: การดักจับ HTTP ภายในกระบวนการ Node เดียวระหว่างการทดสอบอัตโนมัติ ทันทีที่ความต้องการของคุณข้ามขีดจำกัดของกระบวนการ (process boundary) โมเดลนั้นก็เริ่มมีข้อจำกัด
นี่คือข้อจำกัด mock ของ nock อยู่ภายในไฟล์ทดสอบของคุณ ในรันไทม์ของคุณ และในภาษาของคุณ นักพัฒนาฟรอนต์เอนด์ไม่สามารถชี้เบราว์เซอร์ของพวกเขาไปยังการตั้งค่า nock ของคุณได้ วิศวกรโมบายล์ไม่สามารถเรียกใช้มันจากเครื่องจำลอง (simulator) ได้ ทีม QA ของคุณไม่สามารถทำการตรวจสอบด้วยตนเองกับมันได้ คอลเลกชัน Postman ไม่สามารถเข้าถึงได้ mock มีอยู่เฉพาะในขณะที่กระบวนการ Jest หรือ Mocha ของคุณกำลังทำงาน และสำหรับโค้ดในกระบวนการเดียวกันเท่านั้น
นั่นเป็นสิ่งที่ดีสำหรับการทดสอบหน่วย (unit tests) และเป็นสิ่งที่ nock ถูกออกแบบมาเพื่อทำ แต่สถานการณ์จริงหลายอย่างต้องการ mock ที่อยู่ ณ จุดที่ทุกคนสามารถเข้าถึงได้:
- ฟรอนต์เอนด์จำเป็นต้องพัฒนาโดยอ้างอิงจาก endpoint ก่อนที่แบ็กเอนด์จะถูกสร้างขึ้น
- สามทีมที่อยู่ในที่เก็บโค้ด (repos) ที่แตกต่างกันจำเป็นต้องตกลงกันในการตอบสนองปลอมเดียวกัน
- คุณต้องการสาธิตการรวมระบบโดยไม่มีแบ็กเอนด์จริง
- ผู้ทดสอบต้องการตรวจสอบกรณีขอบ (edge cases) ด้วยตนเอง โดยไม่ต้องใช้โค้ด
สำหรับกรณีเหล่านี้ คุณต้องการ เซิร์ฟเวอร์จำลอง (mock server) ซึ่งเป็นบางสิ่งที่คอยรับฟังคำขอจาก URL จริง และส่งคืนการตอบสนองให้กับผู้ที่เรียกใช้มัน หากคุณกำลังชั่งน้ำหนักระหว่างสองแนวคิดนี้ mock server กับ real server และคำอธิบายที่กว้างขึ้นเกี่ยวกับ การจำลอง API (API mocking) ทั้งสองบทความจะอธิบายถึงข้อดีข้อเสีย
nock เทียบกับ hosted mock server (Apidog)
ลองคิดว่ามันเป็นสองเลเยอร์มากกว่าการแข่งขันกัน nock จัดการการดักจับในโค้ดสำหรับการทดสอบหน่วย (unit tests) เครื่องมืออย่าง Apidog มอบเซิร์ฟเวอร์จำลองที่ใช้ร่วมกันและขับเคลื่อนด้วยสคีมา (schema-driven) สำหรับทุกสิ่งที่เกิดขึ้นภายนอกกระบวนการทดสอบเดียว หลายทีมใช้ทั้งสองอย่าง

| nock | Apidog mock server | |
|---|---|---|
| ทำงานที่ไหน | ในกระบวนการทดสอบ Node ของคุณ | เซิร์ฟเวอร์โฮสต์ที่มี URL จริง |
| เหมาะที่สุดสำหรับ | การทดสอบหน่วย, การจำลองข้อผิดพลาด | การรวมระบบ, การทดสอบด้วยตนเอง, การทำงานร่วมกันข้ามทีม |
| ใครเข้าถึงได้ | โค้ดในกระบวนการเดียวกัน | ลูกค้า (client) ใดๆ ที่มี URL |
| การตั้งค่า | โค้ดในแต่ละไฟล์ทดสอบ | สร้างจากสคีมา API ของคุณ |
| ภาษา | Node.js เท่านั้น | ลูกค้า (client) ใดๆ, ภาษาใดๆ |
| ข้อมูลที่สมจริง | คุณเขียนการตอบสนองแต่ละรายการ | การจำลองอัจฉริยะจากสคีมาและชื่อฟิลด์ |
| การแบ่งปัน | ไม่สามารถแชร์ได้ | แชร์ได้ทั้งทีม |
เพื่อความชัดเจนในขอบเขต: Apidog ไม่ได้มาแทนที่ nock ในการทดสอบหน่วย Jest หรือ Mocha ของคุณ หากคุณต้องการดักจับการเรียกใช้ fetch ในการทดสอบเดียวและยืนยันผลลัพธ์ nock ก็ยังคงเป็นเครื่องมือที่เหมาะสม Apidog เข้ามามีบทบาทเมื่อ mock ต้องการที่อยู่ (address) ที่คนอื่นและเครื่องมืออื่นๆ สามารถเข้าถึงได้ สำหรับการดูการทำงานฝั่งเซิร์ฟเวอร์อย่างละเอียด โปรดดู คู่มือเชิงปฏิบัติเกี่ยวกับการจำลอง API สำหรับการทดสอบ คุณสามารถ ดาวน์โหลด Apidog และสร้าง mock จากไฟล์ OpenAPI ที่มีอยู่ได้ในเวลาไม่กี่นาที
ทางเลือกอื่นสำหรับ nock
nock ไม่ใช่ไลบรารีเดียวในพื้นที่นี้ และการเลือกที่เหมาะสมขึ้นอยู่กับว่าโค้ดของคุณทำงานที่ใด
- MSW (Mock Service Worker) ดักจับคำขอที่ระดับเครือข่ายโดยใช้ service worker ในเบราว์เซอร์ และ Node interceptor บนเซิร์ฟเวอร์ คำจำกัดความของ mock เดียวกันสามารถทำงานได้ทั้งสองที่ ซึ่งเป็นเหตุผลว่าทำไมหลายทีมจึงหันมาใช้มันเป็นค่าเริ่มต้นสำหรับ Full-stack JavaScript เอกสารอย่างเป็นทางการของ MSW อธิบายโมเดลนี้
- Jest manual mocks ช่วยให้คุณสามารถแทนที่โมดูลอย่าง
axiosด้วย mock วิธีนี้ง่ายกว่า nock สำหรับกรณีเล็กๆ แต่จะผูกคุณกับ HTTP client เพียงตัวเดียว บทเรียนการจำลอง (mocking) ของ Jest ครอบคลุมรูปแบบนี้ - Built-in test doubles ใน test runner ของ Node เอง หรือไลบรารีอย่าง Sinon สามารถ stub ฟังก์ชันที่ทำการเรียกใช้ได้ แม้ว่าคุณจะเสียความสามารถในการจับคู่ระดับ HTTP ของ nock ไป
คำถามที่ตัดสินใจได้มักจะเหมือนเดิมเสมอ คุณกำลังทดสอบตรรกะภายในกระบวนการเดียว หรือคุณต้องการ API ปลอมที่อยู่บนเครือข่าย? nock และ MSW ตอบคำถามแรก ส่วน hosted mock server ตอบคำถามที่สอง
คำถามที่พบบ่อย
nock ฟรีหรือไม่?
ใช่ nock เป็นโอเพนซอร์สภายใต้ใบอนุญาต MIT และติดตั้งได้ฟรีจาก npm คุณเพิ่มมันเป็น dev dependency และใช้มันในชุดการทดสอบของคุณได้ฟรี
nock ทำงานร่วมกับ fetch และ axios ได้หรือไม่?
ใช่ เพราะ nock ดักจับที่เลเยอร์ http/https ของ Node จึงทำงานร่วมกับไคลเอนต์ใดๆ ที่สร้างขึ้นบนโมดูลเหล่านั้น รวมถึง fetch ดั้งเดิม, axios, got และ node-fetch คุณเขียน interceptor แบบเดียวกันไม่ว่าโค้ดของคุณจะใช้อะไรก็ตาม
ฉันสามารถใช้ nock ในเบราว์เซอร์ได้หรือไม่?
ไม่ได้ nock ใช้ได้กับ Node.js เท่านั้น เพราะมันแก้ไขโมดูล HTTP ของ Node ซึ่งไม่มีอยู่ในเบราว์เซอร์ สำหรับการจำลองฝั่งเบราว์เซอร์ ให้ใช้ MSW หรือชี้ฟรอนต์เอนด์ของคุณไปยัง hosted mock server ภาพรวมของ mock API ใน JavaScript นี้จะอธิบายถึงตัวเลือกสำหรับเบราว์เซอร์
ความแตกต่างระหว่าง nock กับ mock server คืออะไร?
nock ดักจับคำขอภายในกระบวนการทดสอบของคุณและไม่เคยเปิดพอร์ตจริง mock server จะรับฟังคำขอจาก URL จริงที่ไคลเอนต์ใดๆ สามารถเรียกใช้ได้ ใช้ nock สำหรับการทดสอบหน่วย; ใช้ mock server เมื่อฟรอนต์เอนด์, QA หรือทีมอื่นๆ ต้องการเข้าถึงการตอบสนองปลอมชุดเดียวกัน
สรุป
nock เป็นตัวเลือกที่เชื่อถือได้สำหรับการจำลอง HTTP ในการทดสอบหน่วย Node.js มันดักจับคำขอขาออกในกระบวนการ ส่งคืนการตอบสนองที่คุณกำหนด และทำให้ชุดการทดสอบของคุณรวดเร็วและสามารถคาดการณ์ผลลัพธ์ที่แน่นอนได้ (deterministic) รวมถึงเส้นทางข้อผิดพลาดที่คุณไม่สามารถกระตุ้นได้จาก API จริง ใช้มันต่อไปสำหรับวัตถุประสงค์นี้
เมื่อ mock จำเป็นต้องออกจากไฟล์ทดสอบของคุณ เมื่อฟรอนต์เอนด์, QA หรือทีมอื่นต้องเรียกใช้ endpoint เดียวกัน ให้หันไปใช้ shared mock server แทน Apidog สร้าง mock server จากสคีมา API ของคุณและส่งข้อมูลที่สมจริง ณ URL จริง เพื่อให้ทุกคนสร้างตามสัญญาเดียวกันก่อนที่แบ็กเอนด์จะพร้อมใช้งาน ดาวน์โหลด Apidog และเปลี่ยน OpenAPI spec ของคุณให้เป็น mock ที่ใช้งานได้ในไม่กี่นาที
