DEV Community

Cover image for Nock là gì? Mock HTTP request trong Node.js (và giải pháp thay thế không cần code)
Sebastian Petrus
Sebastian Petrus

Posted on • Originally published at apidog.com

Nock là gì? Mock HTTP request trong Node.js (và giải pháp thay thế không cần code)

Nếu các bài kiểm tra Node.js của bạn thất bại vì API bên thứ ba bị sập, chậm hoặc bị giới hạn tốc độ, vấn đề thường không nằm ở code mà nằm ở phụ thuộc mạng. Cách xử lý là giả lập lớp HTTP để unit test chạy ổn định, nhanh và có thể lặp lại. Trong Node.js, nock là thư viện phổ biến để chặn request HTTP trong test. Bài viết này hướng dẫn cách dùng nock với ví dụ thực tế, cách kiểm tra lỗi API và khi nào nên dùng máy chủ giả lập chia sẻ thay vì mock trong tiến trình test.

Thử Apidog ngay hôm nay

nock là gì?

nock là thư viện mock HTTP server cho Node.js. Nó ghi đè các module httphttps của Node trong runtime, sau đó chặn các request gửi ra ngoài và trả về response bạn định nghĩa trước.

Nói cách khác:

  • Code của bạn vẫn gọi fetch, axios, got hoặc node-fetch như bình thường.
  • Request thật không rời khỏi máy.
  • Test nhận về response giả lập có status, body và header đúng như bạn cấu hình.

Điều này đặc biệt hữu ích cho unit test. Khi kiểm tra một hàm gọi API bên ngoài, bạn không muốn phụ thuộc vào dịch vụ thật vì các request thật có thể:

  • chậm;
  • tốn chi phí;
  • bị rate limit;
  • trả lỗi ngẫu nhiên;
  • làm test không ổn định trong CI.

Với nock, bạn có thể mô tả rõ ràng:

Khi code gửi GET https://api.example.com/users/42, hãy trả về HTTP 200 với JSON này.

nock chỉ dành cho Node.js vì nó hook vào HTTP stack của Node. Bạn nên cài nó như một dev dependency, không dùng trong production.

npm install --save-dev nock
Enter fullscreen mode Exit fullscreen mode

Ví dụ cơ bản: mock một API call bằng nock

Giả sử bạn có hàm lấy thông tin người dùng từ 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();
}
Enter fullscreen mode Exit fullscreen mode

Unit test với 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' });
});
Enter fullscreen mode Exit fullscreen mode

Cách đọc test này:

nock('https://api.example.com')
Enter fullscreen mode Exit fullscreen mode

Đặt host cần chặn.

.get('/users/42')
Enter fullscreen mode Exit fullscreen mode

Khớp request GET /users/42.

.reply(200, { id: 42, name: 'Ada Lovelace' });
Enter fullscreen mode Exit fullscreen mode

Trả về status 200 và JSON body đã định nghĩa.

Khi getUser(42) chạy, nock chặn request thật và trả về response giả lập. Không có network call nào xảy ra.

Kiểm tra lỗi API với nock

Một lợi ích lớn của mock HTTP là bạn có thể kiểm tra các lỗi khó tái tạo với API thật, ví dụ lỗi 500.

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');
});
Enter fullscreen mode Exit fullscreen mode

Đây là kiểu test nên có trong các service gọi API bên ngoài:

  • API trả 500;
  • API trả 404;
  • API trả body sai schema;
  • API timeout;
  • API trả dữ liệu rỗng.

Bạn không thể yêu cầu API production trả lỗi 500 đúng lúc test chạy, nhưng với nock thì chỉ cần một dòng .reply(500). Nếu bạn muốn đi sâu hơn về tình huống này, xem thêm hướng dẫn giả lập phản hồi lỗi máy chủ nội bộ 500.

Các pattern nock nên dùng trong test

1. Dọn mock sau mỗi test

Không để interceptor của test này rò rỉ sang test khác.

import nock from 'nock';

afterEach(() => {
  nock.cleanAll();
});
Enter fullscreen mode Exit fullscreen mode

Nếu bạn muốn chặn toàn bộ request thật trong test suite, có thể dùng:

beforeAll(() => {
  nock.disableNetConnect();
});

afterAll(() => {
  nock.enableNetConnect();
});
Enter fullscreen mode Exit fullscreen mode

Điều này giúp phát hiện test nào vô tình gọi API thật.

2. Xác nhận mock đã được gọi

Nếu bạn chỉ khai báo nock nhưng request không bao giờ xảy ra, test vẫn có thể pass nếu bạn không kiểm tra. Dùng scope.done() để bắt lỗi này.

test('calls the expected API endpoint', async () => {
  const scope = nock('https://api.example.com')
    .get('/users/42')
    .reply(200, { id: 42 });

  await getUser(42);

  scope.done();
});
Enter fullscreen mode Exit fullscreen mode

Nếu GET /users/42 không được gọi, scope.done() sẽ làm test fail.

Bạn cũng có thể kiểm tra toàn cục:

expect(nock.isDone()).toBe(true);
Enter fullscreen mode Exit fullscreen mode

3. Mock cùng endpoint nhiều lần

Theo mặc định, một interceptor của nock chỉ dùng một lần. Nếu cùng request được gọi nhiều lần, dùng .times(n).

nock('https://api.example.com')
  .get('/status')
  .times(3)
  .reply(200, { ok: true });
Enter fullscreen mode Exit fullscreen mode

Nếu muốn interceptor tồn tại lâu hơn trong một test cụ thể, dùng .persist().

nock('https://api.example.com')
  .persist()
  .get('/status')
  .reply(200, { ok: true });
Enter fullscreen mode Exit fullscreen mode

Chỉ nên dùng .persist() khi thật sự cần, vì nó có thể che giấu việc code gọi API nhiều hơn dự kiến.

4. Mô phỏng API chậm

Dùng .delay(ms) để test logic timeout hoặc retry.

nock('https://api.example.com')
  .get('/slow')
  .delay(3000)
  .reply(200, { ok: true });
Enter fullscreen mode Exit fullscreen mode

Pattern này hữu ích khi service của bạn có logic như:

  • abort request sau một khoảng thời gian;
  • retry request;
  • fallback sang cache;
  • ghi log khi API chậm.

5. Khớp path động bằng RegExp

Nếu path có ID động, bạn có thể dùng RegExp.

nock('https://api.example.com')
  .get(/\/users\/\d+/)
  .reply(200, { id: 123, name: 'Mock User' });
Enter fullscreen mode Exit fullscreen mode

Hoặc khớp query string:

nock('https://api.example.com')
  .get('/search')
  .query({ q: 'nodejs' })
  .reply(200, { results: [] });
Enter fullscreen mode Exit fullscreen mode

Khi nào nock không còn phù hợp?

nock rất tốt cho một việc: chặn HTTP request bên trong một tiến trình Node.js khi chạy automated test.

Nhưng nock không phải là mock server chia sẻ.

Mock của nock tồn tại:

  • trong file test;
  • trong runtime của Node.js;
  • trong cùng tiến trình với test runner như Jest hoặc Mocha.

Điều đó có nghĩa là:

  • frontend developer không thể mở trình duyệt và gọi mock nock của bạn;
  • mobile developer không thể trỏ app vào mock đó;
  • QA không thể test thủ công trên URL đó;
  • Postman hoặc công cụ ngoài tiến trình không thể truy cập mock đó.

nock phù hợp cho unit test, nhưng nhiều tình huống thực tế cần một mock API có URL thật:

  • frontend cần build trước khi backend sẵn sàng;
  • nhiều team ở nhiều repository cần dùng chung response giả;
  • QA muốn kiểm tra edge case bằng tay;
  • bạn cần demo integration mà không phụ thuộc backend thật;
  • contract API cần được chia sẻ trước khi implementation hoàn tất.

Trong các trường hợp đó, bạn cần một máy chủ giả lập chạy trên URL thật. Nếu bạn đang cân nhắc giữa các lựa chọn, xem thêm máy chủ giả lập so với máy chủ thực và tổng quan về giả lập API.

nock so với máy chủ giả lập được lưu trữ như Apidog

Hãy xem nock và máy chủ giả lập được lưu trữ là hai lớp khác nhau:

  • nock: dùng trong unit test Node.js để chặn HTTP request trong tiến trình.
  • Apidog: dùng để tạo mock server chia sẻ từ API schema, có URL thật cho nhiều client và nhiều team.

Nhiều team dùng cả hai: nock cho unit test, Apidog cho frontend, QA, demo và tích hợp giữa các nhóm.

Với Apidog, bạn định nghĩa endpoint và schema API, sau đó tạo mock server từ thiết kế đó. Mock server trả response tại một URL trực tiếp, giúp bất kỳ client nào cũng có thể gọi được.

nock Máy chủ giả lập Apidog
Nơi chạy Trong tiến trình kiểm thử Node.js Máy chủ được lưu trữ với URL thật
Phù hợp nhất cho Unit test, mô phỏng lỗi Tích hợp, kiểm thử thủ công, làm việc giữa nhiều nhóm
Ai có thể truy cập Code trong cùng tiến trình Bất kỳ client nào có URL
Cách thiết lập Viết code mock trong từng file test Tạo từ schema API
Ngôn ngữ Node.js Bất kỳ client hoặc ngôn ngữ nào
Dữ liệu giả lập Bạn tự viết từng response Tạo dữ liệu từ schema và tên trường
Chia sẻ Không phù hợp để chia sẻ ngoài tiến trình test Chia sẻ được cho toàn bộ nhóm

Apidog không thay thế nock trong unit test Jest hoặc Mocha. Nếu bạn cần chặn một request fetch trong một test cụ thể và assert kết quả, nock vẫn là lựa chọn đúng.

Apidog phù hợp khi mock cần có địa chỉ mà người khác hoặc công cụ khác có thể truy cập. Để xem thêm cách áp dụng ở phía server, đọc hướng dẫn thực tế về giả lập API để kiểm thử. Bạn cũng có thể tải xuống Apidog và tạo mock từ file OpenAPI hiện có.

Các lựa chọn thay thế nock

nock không phải là lựa chọn duy nhất. Công cụ phù hợp phụ thuộc vào nơi code của bạn chạy và loại test bạn cần.

MSW

MSW (Mock Service Worker) chặn request ở cấp mạng bằng service worker trong trình duyệt và interceptor trong Node.js. Một bộ mock handler có thể dùng cho cả browser và server-side test.

MSW thường phù hợp khi bạn muốn mock API cho ứng dụng JavaScript full-stack hoặc frontend test. Xem thêm tại tài liệu chính thức của MSW.

Jest manual mocks

Jest cho phép mock module thủ công, ví dụ thay thế axios bằng một module giả.

jest.mock('axios');
Enter fullscreen mode Exit fullscreen mode

Cách này đơn giản cho case nhỏ, nhưng bạn đang mock ở cấp module, không phải cấp HTTP. Nếu code đổi từ axios sang fetch, test cũng phải đổi theo. Xem thêm hướng dẫn giả lập của Jest.

Test doubles trong Node.js hoặc Sinon

Bạn cũng có thể mock function thực hiện API call bằng test double từ Node.js test runner hoặc Sinon. Cách này phù hợp nếu bạn muốn cô lập logic ở cấp function, nhưng sẽ mất khả năng khớp request HTTP theo host, path, query và method như nock.

Câu hỏi thường gặp

nock có miễn phí không?

Có. nock là mã nguồn mở theo giấy phép MIT và miễn phí cài đặt từ npm. Bạn nên thêm nó dưới dạng dev dependency.

npm install --save-dev nock
Enter fullscreen mode Exit fullscreen mode

nock có hoạt động với fetch và axios không?

Có. Vì nock chặn ở lớp http/https của Node.js, nó hoạt động với các HTTP client dựa trên các module này, bao gồm fetch, axios, gotnode-fetch.

Có thể dùng nock trong trình duyệt không?

Không. nock chỉ dành cho Node.js vì nó vá các module HTTP của Node, vốn không tồn tại trong trình duyệt.

Nếu cần mock API trong trình duyệt, hãy dùng MSW hoặc trỏ frontend đến một mock server được lưu trữ. Tổng quan về các API giả lập trong JavaScript sẽ giúp bạn chọn hướng phù hợp.

Sự khác biệt giữa nock và mock server là gì?

nock chặn request bên trong tiến trình test Node.js và không mở cổng mạng thật.

Mock server lắng nghe trên một URL thật mà bất kỳ client nào cũng có thể gọi.

Quy tắc thực tế:

  • dùng nock cho unit test Node.js;
  • dùng mock server khi frontend, QA, mobile app hoặc team khác cần truy cập cùng response giả.

Tổng kết

nock là lựa chọn đáng tin cậy để mock HTTP trong unit test Node.js. Nó giúp test nhanh hơn, ổn định hơn và dễ kiểm tra các lỗi như 500, timeout hoặc response không hợp lệ.

Dùng nock khi bạn cần chặn request trong một test cụ thể.

Khi mock cần rời khỏi file test và trở thành một URL dùng chung cho frontend, QA hoặc nhiều nhóm, hãy dùng mock server được lưu trữ. Apidog có thể tạo mock server từ API schema và phục vụ dữ liệu tại URL trực tiếp. Bạn có thể tải xuống Apidog để biến thông số OpenAPI thành mock API có thể dùng được.

Top comments (0)