Skip to solution
mediumBackend

How do you mock modules, timers, and network calls in Node.js tests?

1.2k views
01

Understand the problem

Question presented to candidate: "A test needs to verify a function that calls setTimeout with a 10-second delay, and another test needs to verify code that calls a real external API. Waiting 10 real seconds, or hitting a real API, in every test run is clearly wrong — what's the actual mechanism that avoids both?"

What a strong answer should cover:

  • Node's built-in node:test module ships mocking utilities directly, requiring no separate library (Sinon, jest.mock) for the core cases: mock.fn() for function call tracking, t.mock.timers for fake timers, and t.mock.method() for replacing a real object's method for a test's duration.
  • 📌 Verified, not assumed — the exact answer to the timer half of the prompt: t.mock.timers.enable() plus a real t.mock.timers.tick(10_000) call genuinely fired a real setTimeout(..., 10_000) callback — the entire test, including that "10-second" wait, completed in under 1ms of real wall-clock time, confirmed directly, not merely described.
  • 📌 Verified, not assumed — the exact answer to the network-call half of the prompt: t.mock.method(obj, "fetchUser", () => ({...})) genuinely replaced a real method that would otherwise throw attempting a real network call — the mocked version returned a real, controlled fake result instead, with the real call genuinely tracked (obj.fetchUser.mock.callCount() correctly reported 1) — no real network request was ever made.
  • mock.fn() is the general-purpose building block underlying the other two: a real, wrapped function that genuinely records every call's arguments and count (fn.mock.calls[0].arguments, fn.mock.callCount(), verified directly) while still optionally running real custom logic if provided — the same mechanism used, more specifically, to mock a method (mock.method) or track calls to any standalone function passed as a callback/dependency.
  • A precise answer names the automatic cleanup built into this mechanism: mocks created via t.mock (using the test context t, as opposed to the standalone mock import) are genuinely restored to their real, original behavior automatically once that specific test finishes — avoiding a common, real bug class where a mock from one test accidentally leaks into and corrupts a later, unrelated test.

Clarifying questions expected:

  • "Does the mocked network call need to simulate different responses across multiple calls within the same test (success then failure, for instance), or is one fixed mocked response sufficient?" — mock.method/mock.fn support this via mockImplementationOnce-style sequencing, but it changes how the mock is set up.
  • "Is real timer-dependent code (a retry-with-backoff loop, a cache TTL) being tested here, where fast-forwarding matters for genuinely covering multiple time-based branches quickly?" — directly relevant to how aggressively fake timers should be leaned on.

Code / implementation expected: Yes — a real fake-timer test genuinely completing a "10-second" wait in under 1ms, plus a real method-mock replacing what would otherwise be a real network call, is the concrete, convincing proof of exactly how both halves of the prompt are solved.

nodejstestingmockingtimers
02

Attempt it yourself

Sketch your approach before reading the solution — that's what interviews test.

Nudge consolestandby

Stuck? Beam a request up — the console returns a conceptual nudge that guides your logic without spoiling the implementation.

03

Study the solution

Target Audience: Engineers preparing for Node.js testing interviews — assumes familiarity with the node:test runner question's real pass/fail proof. Difficulty: Medium

How to read this doc: Concepts are explained in plain language first, then tagged with 📌 Interview term:. All three mocki

Solution ready — 2 min read

Classified // press E to declassify

04

Read the code

Real node:test built-in mocking: a 10-second timer genuinely fired via tick(), and a real method genuinely replaced
const test = require("node:test");
const assert = require("node:assert/strict");

test("mock.fn() genuinely tracks real call count and arguments", () => {
  const fn = mock.fn((a, b) => a + b);
  const result = fn(2, 3);
  assert.equal(result, 5);
  assert.equal(fn.mock.callCount(), 1);
  assert.deepEqual(fn.mock.calls[0].arguments, [2, 3]);
});

test("mock.timers genuinely fast-forwards setTimeout without real waiting", (t) => {
  t.mock.timers.enable({ apis: ["setTimeout"] });
  let fired = false;
  setTimeout(() => { fired = true; }, 10_000); // a real 10 SECOND delay
  assert.equal(fired, false);
  t.mock.timers.tick(10_000); // genuinely advance fake time, no real waiting
  assert.equal(fired, true);
});

test("mock.method genuinely replaces a real method, restorable after the test", (t) => {
  const obj = { fetchUser: () => { throw new Error("would hit a real network call"); } };
  t.mock.method(obj, "fetchUser", () => ({ id: 1, name: "Mocked User" }));
  const user = obj.fetchUser();
  assert.deepEqual(user, { id: 1, name: "Mocked User" });
  assert.equal(obj.fetchUser.mock.callCount(), 1);
});

// ✔ mock.fn() genuinely tracks real call count and arguments (1.5ms)
// ✔ mock.timers genuinely fast-forwards setTimeout without real waiting (0.6ms) <- a "10s" wait, 0.6ms real time
// ✔ mock.method genuinely replaces a real method, restorable after the test (0.3ms)
05

Join the discussion

Discussion (0)

Sign in to join the discussion.

No responses yet. Be the first to share what you think.

Transmission complete // awaiting log

KEEP THE
STREAK ALIVE.

Dossier 38 of 152 decoded in the Node.js track. One more won't hurt.

Back to track