Block real HTTP calls in tests to keep them deterministic, fast, and safe by forcing every outbound request to be mocked.

Deterministic tests do not call the public internet. They mock it. Blocking outbound HTTP makes every suite faster, safer, and easier to debug.

Why block network access?

  • Determinism: Live APIs change, rate-limit, or fail. Tests should be predictable.
  • Speed: External calls slow down runs; blocking them forces you to mock.
  • Isolation: Unit tests should validate your logic, not remote services.
  • Cost & safety: Avoid hitting paid APIs or mutating real data by accident.
  • Rule of thumb: If a test makes a real HTTP call, it isn’t a true unit test.

Core idea

  • Intercept HTTP clients (fetch, axios, requests, http/https) at test startup.
  • Allow only localhost for in-memory servers or mocks.
  • Throw a clear error when an outbound call slips through.
  • Restore originals after tests to avoid side effects.

Example: Jest (Node/JS)

Add a global setup file (e.g., jest.setup.js) and reference it in setupFilesAfterEnv.

// jest.setup.js
import http from 'http';
import https from 'https';

const originalHttpRequest = http.request;
const originalHttpGet = http.get;
const originalHttpsRequest = https.request;
const originalHttpsGet = https.get;
const originalFetch = global.fetch;

const isLocalhost = (host) =>
  host &&
  (host.startsWith('localhost') || host.startsWith('127.0.0.1') || host.startsWith('[::1]'));

const blockRequest = (protocol, originalReq) => function (...args) {
  const first = args[0];
  let host;
  if (typeof first === 'string') host = new URL(first, `${protocol}://placeholder`).host;
  else if (first && typeof first === 'object') host = first.host ?? first.hostname;
  if (!host || !isLocalhost(host)) {
    throw new Error('Network access is blocked during tests. Please mock HTTP calls.');
  }
  return originalReq.apply(this, args);
};

beforeAll(() => {
  http.request = blockRequest('http', originalHttpRequest);
  http.get = blockRequest('http', originalHttpGet);
  https.request = blockRequest('https', originalHttpsRequest);
  https.get = blockRequest('https', originalHttpsGet);
  if (typeof originalFetch === 'function') {
    global.fetch = (input, init) => {
      const url = typeof input === 'string' ? input : input?.url;
      const host = url ? new URL(url).host : undefined;
      if (!host || !isLocalhost(host)) {
        throw new Error('Network access is blocked during tests. Please mock HTTP calls.');
      }
      return originalFetch(input, init);
    };
  }
});

afterAll(() => {
  http.request = originalHttpRequest;
  http.get = originalHttpGet;
  https.request = originalHttpsRequest;
  https.get = originalHttpsGet;
  if (originalFetch) global.fetch = originalFetch;
});

Then, in jest.config.js:

setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],

Mocking example in a test:

import fetch from 'node-fetch';
jest.mock('node-fetch');

test('uses mocked API', async () => {
  fetch.mockResolvedValue({ ok: true, json: async () => ({ msg: 'hi' }) });
  const res = await fetch('https://api.example.com/data'); // will use mock
  expect(await res.json()).toEqual({ msg: 'hi' });
});

If you forget to mock, the test throws: “Network access is blocked during tests. Please mock HTTP calls.”

Example: Pytest (Python)

Add an autouse fixture in conftest.py to block outbound requests with requests.

# conftest.py
import pytest
import requests


@pytest.fixture(autouse=True)
def block_network(monkeypatch):
    def _blocked(*args, **kwargs):
        raise AssertionError("Network access is blocked during tests. Please mock HTTP calls.")

    monkeypatch.setattr(requests, "get", _blocked)
    monkeypatch.setattr(requests, "post", _blocked)
    monkeypatch.setattr(requests, "put", _blocked)
    monkeypatch.setattr(requests, "delete", _blocked)
    monkeypatch.setattr(requests, "request", _blocked)

Mocking example:

from types import SimpleNamespace


def test_calls_api(monkeypatch):
    def fake_get(url, *a, **k):
        return SimpleNamespace(status_code=200, json=lambda: {"msg": "hi"})

    import requests
    monkeypatch.setattr(requests, "get", fake_get)

    resp = requests.get("https://api.example.com/data")
    assert resp.json() == {"msg": "hi"}

Unmocked calls raise the blocking error, forcing you to stub the API.

Tips and patterns

  • Allow localhost only for in-memory servers (e.g., supertest).
  • Keep the error message obvious so developers know to mock.
  • Restore originals to avoid leaking patched globals across tests.
  • Mock per-test or per-suite where the API is used; keep helpers nearby.
  • Use small factory helpers to stub common responses (200, 404, 500, throttling).

Takeaways

Blocking real network calls makes tests faster, deterministic, and safer. Enforce it globally so accidental calls fail fast, and mock APIs at the edges so you test your logic—not the internet.

FAQ

Is it ever okay to allow external network access in tests?
Only for a small set of contract or smoke tests that run separately from unit tests. Keep the default as blocked, and explicitly opt-in when you truly need to hit a real service.
How do I let localhost through for in-memory servers?
Treat localhost as an allowlisted host (127.0.0.1, [::1]) in your blocker so tests that start their own servers can still run.

Welcome to The infinite monkey theorem

Somewhere a monkey just typed Shakespeare in TypeScript. Be the first to read the masterpieces (and the hilarious misfires) landing on the blog.

Subscribe to The infinite monkey theorem

We fling fresh posts—no banana peels attached—straight to your inbox.