Last Updated: 3/9/2026
Testing Applications
Testing is essential for maintaining reliable Hono applications. Hono makes testing straightforward—you create a Request, pass it to your app, and validate the Response. The same testing approach works across all runtimes.
Testing with app.request()
The app.request() method is the primary tool for testing Hono applications. It simulates HTTP requests without needing a running server.
Basic GET Request Test
Consider an application with this endpoint:
app.get('/posts', (c) => {
return c.text('Many posts')
})Test it like this:
import { describe, test, expect } from 'vitest'
describe('GET /posts', () => {
test('returns 200 with text response', async () => {
const res = await app.request('/posts')
expect(res.status).toBe(200)
expect(await res.text()).toBe('Many posts')
})
})Testing POST Requests
For a POST endpoint:
app.post('/posts', (c) => {
return c.json(
{ message: 'Created' },
201,
{ 'X-Custom': 'Thank you' }
)
})Test the status, headers, and JSON response:
test('POST /posts returns 201', async () => {
const res = await app.request('/posts', {
method: 'POST',
})
expect(res.status).toBe(201)
expect(res.headers.get('X-Custom')).toBe('Thank you')
expect(await res.json()).toEqual({
message: 'Created',
})
})Testing with Request Bodies
JSON Request Body
When sending JSON data, include the Content-Type header:
test('POST /posts with JSON body', async () => {
const res = await app.request('/posts', {
method: 'POST',
body: JSON.stringify({ title: 'Hello Hono' }),
headers: new Headers({ 'Content-Type': 'application/json' }),
})
expect(res.status).toBe(201)
expect(await res.json()).toEqual({
message: 'Created',
})
})Note: The
Content-Typeheader is required when testing JSON or form validation. Without it, the request body will not be parsed correctly.
Form Data Request Body
For multipart form data:
test('POST /posts with form data', async () => {
const formData = new FormData()
formData.append('title', 'Hello')
formData.append('body', 'Hono is great')
const res = await app.request('/posts', {
method: 'POST',
body: formData,
})
expect(res.status).toBe(201)
})Using Request Objects
You can also pass a complete Request object:
test('POST /posts with Request object', async () => {
const req = new Request('http://localhost/posts', {
method: 'POST',
body: JSON.stringify({ title: 'Test' }),
headers: { 'Content-Type': 'application/json' },
})
const res = await app.request(req)
expect(res.status).toBe(201)
})Mocking Environment Variables
To test code that uses c.env, pass a mock environment object as the third parameter:
const MOCK_ENV = {
API_HOST: 'example.com',
DB: {
prepare: () => {
/* mocked D1 database */
},
},
}
test('GET /posts with mocked env', async () => {
const res = await app.request('/posts', {}, MOCK_ENV)
expect(res.status).toBe(200)
})This is particularly useful for testing Cloudflare Workers bindings like KV, D1, or R2.
Testing Middleware
Test middleware by verifying its effects on the response:
import { basicAuth } from 'hono/basic-auth'
app.use(
'/admin/*',
basicAuth({
username: 'admin',
password: 'secret',
})
)
app.get('/admin/dashboard', (c) => c.text('Admin Dashboard'))
test('requires authentication', async () => {
// Without credentials
const res1 = await app.request('/admin/dashboard')
expect(res1.status).toBe(401)
// With credentials
const credentials = btoa('admin:secret')
const res2 = await app.request('/admin/dashboard', {
headers: { Authorization: `Basic ${credentials}` },
})
expect(res2.status).toBe(200)
})Testing Error Handling
Test custom error handlers:
app.onError((err, c) => {
return c.json({ error: err.message }, 500)
})
app.get('/error', () => {
throw new Error('Something went wrong')
})
test('handles errors correctly', async () => {
const res = await app.request('/error')
expect(res.status).toBe(500)
expect(await res.json()).toEqual({
error: 'Something went wrong',
})
})Testing with Vitest
For Cloudflare Workers, use @cloudflare/vitest-pool-workers for the most accurate testing environment.
Install it:
npm i -D vitest @cloudflare/vitest-pool-workersConfigure vitest.config.ts:
import { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config'
export default defineWorkersConfig({
test: {
poolOptions: {
workers: {
wrangler: { configPath: './wrangler.toml' },
},
},
},
})See the Cloudflare Vitest integration docs for complete setup instructions.
Type-Safe Testing with Testing Helper
For advanced type-safe testing, use the testing helper which provides typed test clients.
What’s Next
- Best Practices - Learn patterns for structuring larger applications
- RPC Client - Build type-safe API clients
- Deployment Guides - Deploy your tested application