LogoRobo.js

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:

tests/api/users.test.ts
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)
})
tests/api/users.test.js
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:

tests/api/users.test.ts
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')
})
tests/api/users.test.js
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:

tests/integration.test.ts
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 } })
})
tests/integration.test.js
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

OptionTypeDefaultDescription
methodstring'GET'HTTP method
pathstring'/test'URL path
paramsRecord<string, string>URL parameters
queryRecord<string, string | string[]>Query string parameters
headersRecord<string, string>Request headers
bodyunknownRequest body (auto-serialized to JSON)
baseUrlstring'http://localhost:3000'Base URL

TestRouteResult

Property/MethodTypeDescription
statusnumberHTTP status code
okbooleantrue if status is 2xx
json()Promise<T>Parse response body as JSON
text()Promise<string>Get response body as text
header(name)string | nullGet a response header value
responseResponseRaw Response object

Next steps

On this page