app.inject — Zero-Network Testing

Hit Your Routes In-Process

app.inject — Zero-Network Testing

app.inject simulates an HTTP request without binding a port — the fastest way to test Fastify apps.

4 min read Level 2/5 #fastify#testing#inject
What you'll learn
  • Build the app in tests
  • Call app.inject with method, url, and payload
  • Assert on statusCode and json()

Fastify ships with a built-in test helper called app.inject(). It simulates an HTTP request through the same router and lifecycle as a real socket, but never binds a port — so tests are fast, parallel-safe, and don’t fight over port 3000.

A First Test

// test/hello.test.ts
import { test } from 'node:test';
import assert from 'node:assert/strict';
import Fastify from 'fastify';

test('GET / returns { ok: true }', async () => {
  const app = Fastify();
  app.get('/', async () => ({ ok: true }));

  const res = await app.inject({ method: 'GET', url: '/' });

  assert.strictEqual(res.statusCode, 200);
  assert.deepEqual(res.json(), { ok: true });
});

No app.listen, no fetch, no port. inject returns a LightMyRequest response with statusCode, headers, body, and a json() helper.

Posting JSON

const res = await app.inject({
  method: 'POST',
  url: '/users',
  payload: { email: 'ada@x.dev', password: 'secret123' },
});

assert.strictEqual(res.statusCode, 201);
assert.match(res.json().id, /^[0-9a-f-]{36}$/);

payload is serialized to JSON and the content-type header is set automatically.

Headers, Cookies, Query

const res = await app.inject({
  method: 'GET',
  url: '/me',
  headers: { authorization: 'Bearer abc' },
  cookies: { sid: 's_123' },
  query: { include: 'profile' },
});

inject accepts everything a real request needs. Use it as your default — reach for real sockets only when something forces you to.

supertest With Fastify →