This episode covers testing the API and HTTP layers: mocking requests with fetch, axios, and nock, testing API client modules, running integration tests with test servers, and isolation strategies to avoid flaky tests.

Code that talks to external servers is a major source of flaky tests: slow networks, down servers, or changing data can make tests fail for no reason. Episode 12 covers the right strategies for testing API & HTTP layers — mocking requests with fetch, axios, and nock, testing API client modules in isolation, running integration tests with test servers, and maintaining isolation so the suite stays deterministic.
The end goal is clear: fast, deterministic tests that don't depend on network conditions. Let's break down the techniques one by one.
For code that uses fetch, mock the global fetch with jest.fn:
global.fetch = jest.fn();
async function ambilPengguna(id) {
const res = await fetch(`/api/pengguna/${id}`);
return res.json();
}
test("mengambil data pengguna", async () => {
fetch.mockResolvedValue({
ok: true,
json: async () => ({ id: 1, nama: "arif" }),
});
const hasil = await ambilPengguna(1);
expect(hasil.nama).toBe("arif");
expect(fetch).toHaveBeenCalledWith("/api/pengguna/1");
});fetch.mockResolvedValue({...}) makes fetch return a predefined response, so the test never touches the network. The toHaveBeenCalledWith assertion verifies the URL and parameters used.
For axios, mocking is easier through a module mock:
jest.mock("axios");
import axios from "axios";
axios.get.mockResolvedValue({ data: { pesan: "ok" } });
test("axios.get mengembalikan data", async () => {
const { data } = await axios.get("/api/cek");
expect(data.pesan).toBe("ok");
});jest.mock("axios") creates an automatic mock for the entire module. Because axios is an external module, this replaces all its functions with jest.fn that you can configure to behave however you need.
nock takes a different approach: it intercepts real HTTP requests at the transport level, rather than replacing functions:
npm install --save-dev nockimport nock from "nock";
test("request dicegat nock", async () => {
nock("https://api.example.com")
.get("/status")
.reply(200, { status: "ok" });
const res = await fetch("https://api.example.com/status");
expect(await res.json()).toEqual({ status: "ok" });
nock.cleanAll();
});nock("https://api.example.com") creates an interceptor for that host. Its advantage: you don't need to modify the code under test — requests still run normally, they're just intercepted before leaving the process.
API client modules are easiest to test when they have a single responsibility: building URLs, setting headers, sending requests, and parsing responses. Mock the transport (fetch or axios), then focus on testing the client logic itself:
function apiClient(baseUrl) {
return {
getStatus: async () => {
const res = await fetch(`${baseUrl}/status`);
if (!res.ok) {
throw new Error(`HTTP ${res.status}`);
}
return res.json();
},
};
}With the transport mocked, you can test the client's behavior across various scenarios: ok responses, error statuses, timeouts, and invalid JSON — without a real server.
Sometimes an integration test needs a real server. The most common solution: a lightweight HTTP server started inside the test:
import { createServer } from "node:http";
test("end-to-end dengan server lokal", async () => {
const server = createServer((req, res) => {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ pesan: "dari server" }));
});
await new Promise((resolve) => server.listen(0, resolve));
const port = server.address().port;
const res = await fetch(`http://localhost:${port}/`);
expect(await res.json()).toEqual({ pesan: "dari server" });
await new Promise((resolve) => server.close(resolve));
});server.listen(0, resolve) requests a random port so tests don't collide. This integration test exercises the entire request stack — including body parsing and status codes — without external networks.
A few rules to keep network tests isolated:
afterEach with jest.clearAllMocks() or nock.cleanAll().When a test suddenly fails because of "the network", that's a sign of a wrong design. A good test doesn't care whether the network is up or down.
Episode 12 equipped you with API and HTTP layer testing: mocking fetch and axios, intercepting requests with nock, testing API client modules in isolation, and running integration tests with local test servers.
Key takeaways:
jest.fn so tests never touch the network.jest.mock("axios") creates an automatic mock for external modules.afterEach to keep tests isolated.In the next episode, episode 13, we'll cover security & test reliability — preventing tests from leaking sensitive data, isolating environment-specific logic, making tests deterministic and repeatable, and avoiding flaky tests with proper setup and teardown.