Testing
Unit test your API routes without starting a server.
The @robojs/server/testing module provides utilities for testing API endpoints without launching a server. Tests are fast, isolated, and don't require network access.
Setup
Import testing utilities from the dedicated subpath:
import { createTestRequest, testRoute, testHandler, createTestClient } from '@robojs/server/testing'import { createTestRequest, testRoute, testHandler, createTestClient } from '@robojs/server/testing'Testing individual handlers
Use createTestRequest to create a RoboRequest and call your handler directly:
import { createTestRequest } from '@robojs/server/testing'
import { GET, POST } from '../src/api/users/[id]'
test('GET returns user by ID', async () => {
const request = createTestRequest({
params: { id: '123' },
query: { include: 'profile' }
})
const response = await GET(request)
expect(response).toEqual({ id: '123', include: 'profile' })
})
test('POST creates a user', async () => {
const request = createTestRequest({
method: 'POST',
params: { id: '123' },
body: { name: 'Alice' }
})
const response = await POST(request)
expect(response.created).toBe(true)
})import { createTestRequest } from '@robojs/server/testing'
import { GET, POST } from '../src/api/users/[id]'
test('GET returns user by ID', async () => {
const request = createTestRequest({
params: { id: '123' },
query: { include: 'profile' }
})
const response = await GET(request)
expect(response).toEqual({ id: '123', include: 'profile' })
})
test('POST creates a user', async () => {
const request = createTestRequest({
method: 'POST',
params: { id: '123' },
body: { name: 'Alice' }
})
const response = await POST(request)
expect(response.created).toBe(true)
})Testing route modules
Use testRoute to test a route module with automatic method dispatch:
import { testRoute } from '@robojs/server/testing'
import * as usersRoute from '../src/api/users/[id]'
test('POST dispatches to POST handler', async () => {
const result = await testRoute(usersRoute, {
method: 'POST',
params: { id: '123' },
body: { name: 'Alice' }
})
expect(result.status).toBe(200)
expect(result.ok).toBe(true)
expect(await result.json()).toEqual({ id: '123', name: 'Alice' })
expect(result.header('Content-Type')).toBe('application/json')
})import { testRoute } from '@robojs/server/testing'
import * as usersRoute from '../src/api/users/[id]'
test('POST dispatches to POST handler', async () => {
const result = await testRoute(usersRoute, {
method: 'POST',
params: { id: '123' },
body: { name: 'Alice' }
})
expect(result.status).toBe(200)
expect(result.ok).toBe(true)
expect(await result.json()).toEqual({ id: '123', name: 'Alice' })
expect(result.header('Content-Type')).toBe('application/json')
})testRoute uses the same method dispatch logic as the real server — including auto OPTIONS, auto HEAD-from-GET, and 405 for unsupported methods.
Testing raw return values
Use testHandler when you want the raw return value before Response wrapping:
import { testHandler } from '@robojs/server/testing'
import { GET } from '../src/api/users/[id]'
test('GET returns raw user object', async () => {
const result = await testHandler(GET, {
params: { id: '123' }
})
expect(result).toEqual({ id: '123', name: 'Alice' })
})import { testHandler } from '@robojs/server/testing'
import { GET } from '../src/api/users/[id]'
test('GET returns raw user object', async () => {
const result = await testHandler(GET, {
params: { id: '123' }
})
expect(result).toEqual({ id: '123', name: 'Alice' })
})Testing multiple routes
Use createTestClient for integration-style testing:
import { createTestClient } from '@robojs/server/testing'
import * as usersRoute from '../src/api/users/[id]'
import * as postsRoute from '../src/api/posts'
const client = createTestClient()
.route('users/[id]', usersRoute)
.route('posts', postsRoute)
test('full user flow', async () => {
const user = await client.get('/users/123')
expect(user.status).toBe(200)
const post = await client.post('/posts', {
body: { title: 'Hello', authorId: '123' }
})
expect(post.status).toBe(201)
await client.put('/users/123', { body: { name: 'Updated' } })
await client.delete('/users/123')
await client.patch('/users/123', { body: { active: false } })
})import { createTestClient } from '@robojs/server/testing'
import * as usersRoute from '../src/api/users/[id]'
import * as postsRoute from '../src/api/posts'
const client = createTestClient()
.route('users/[id]', usersRoute)
.route('posts', postsRoute)
test('full user flow', async () => {
const user = await client.get('/users/123')
expect(user.status).toBe(200)
const post = await client.post('/posts', {
body: { title: 'Hello', authorId: '123' }
})
expect(post.status).toBe(201)
await client.put('/users/123', { body: { name: 'Updated' } })
await client.delete('/users/123')
await client.patch('/users/123', { body: { active: false } })
})The client automatically extracts parameters from the URL path based on the registered route patterns.
Request options
| Option | Type | Default | Description |
|---|---|---|---|
method | string | 'GET' | HTTP method |
path | string | '/test' | URL path |
params | Record<string, string> | — | URL parameters |
query | Record<string, string | string[]> | — | Query string parameters |
headers | Record<string, string> | — | Request headers |
body | unknown | — | Request body (auto-serialized to JSON) |
baseUrl | string | 'http://localhost:3000' | Base URL |
TestRouteResult
| Property/Method | Type | Description |
|---|---|---|
status | number | HTTP status code |
ok | boolean | true if status is 2xx |
json() | Promise<T> | Parse response body as JSON |
text() | Promise<string> | Get response body as text |
header(name) | string | null | Get a response header value |
response | Response | Raw Response object |
