Testing
Testing is a first-class feature of Server.js. Call .test() on your app to get a lightweight test client that runs requests through the full middleware stack without starting an HTTP server.
Basic setup
Keep your server in its own file so tests can import it:
// src/index.js
import server from '@server/next';
export default server()
.get('/', () => 'Hello world')
.get('/users', () => User.list())
.post('/users', (ctx) => User.create(ctx.body));// src/index.test.js
import app from './index.js';
const api = app.test();
it('returns hello world', async () => {
const res = await api.get('/');
expect(await res.text()).toBe('Hello world');
});Available methods
The test client mirrors the HTTP methods. Each call returns a standard Response:
const api = app.test();
api.get('/path', options?)
api.post('/path', body?, options?)
api.put('/path', body?, options?)
api.patch('/path', body?, options?)
api.delete('/path', options?)
api.head('/path', options?)
api.options('/path', options?)body: the request body. Accepts a string, plain object (serialized as JSON),FormData, orReadableStream.options: standardRequestInitoptions (e.g.headers).
Reading the response
Each method returns a standard web Response:
const res = await api.get('/users');
res.status // 200
res.headers.get('content-type') // 'application/json'
await res.text() // raw body as string
await res.json() // parsed JSON bodySending a body
Pass a plain object and Server.js will serialize it as JSON and set Content-Type: application/json automatically:
const res = await api.post('/users', {
name: 'Francisco',
email: '[email protected]',
});
expect(res.status).toBe(201);
expect(await res.json()).toMatchObject({
name: 'Francisco',
});For other body types:
// Plain text
await api.post('/echo', 'Hello world');
// FormData (e.g. file uploads)
const form = new FormData();
form.append('name', 'Francisco');
await api.post('/upload', form);
// Custom headers
await api.get('/secure', {
headers: { authorization: 'Bearer token123' },
});Full example
// src/books.test.js
import app from './index.js';
const api = app.test();
describe('books API', () => {
it('lists books', async () => {
const res = await api.get('/books');
expect(res.status).toBe(200);
expect(await res.json()).toEqual([]);
});
it('creates a book', async () => {
const res = await api.post('/books', {
title: 'Dune',
author: 'Herbert',
});
expect(res.status).toBe(201);
const body = await res.json();
expect(body.title).toBe('Dune');
});
it('returns 404 for unknown books', async () => {
const res = await api.get('/books/9999');
expect(res.status).toBe(404);
});
it('deletes a book', async () => {
const res = await api.delete('/books/1');
expect(res.status).toBe(204);
});
});Limitations
The test client simulates requests in-process; it does not open a real TCP connection. This means:
- Compression (
brotli,gzip) andcontent-encodingheaders are not applied (those are usually set by edge proxies). - Edge-only features like
cf-*headers won't be present.
For integration tests that require a real HTTP connection, start the server on a test port and use fetch directly.