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.
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.