Last Updated: 3/11/2026
Testing
Testing Hono applications is straightforward. Create requests, pass them to your app, and validate responses.
Basic Testing
Use app.request() to test your routes:
import { describe, test, expect } from 'vitest'
import { Hono } from 'hono'
const app = new Hono()
app.get('/posts', (c) => {
return c.text('Many posts')
})
describe('Example', () => {
test('GET /posts', async () => {
const res = await app.request('/posts')
expect(res.status).toBe(200)
expect(await res.text()).toBe('Many posts')
})
})Testing POST Requests
Simple POST
app.post('/posts', (c) => {
return c.json({ message: 'Created' }, 201, {
'X-Custom': 'Thank you',
})
})
test('POST /posts', 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' })
})POST with JSON
test('POST /posts with JSON', async () => {
const res = await app.request('/posts', {
method: 'POST',
body: JSON.stringify({ message: 'hello hono' }),
headers: new Headers({ 'Content-Type': 'application/json' }),
})
expect(res.status).toBe(201)
expect(await res.json()).toEqual({ message: 'Created' })
})POST with Form Data
test('POST /posts with form data', async () => {
const formData = new FormData()
formData.append('message', 'hello')
const res = await app.request('/posts', {
method: 'POST',
body: formData,
})
expect(res.status).toBe(201)
})Using Request Objects
Pass a Request instance directly:
test('POST /posts with Request', async () => {
const req = new Request('http://localhost/posts', {
method: 'POST',
})
const res = await app.request(req)
expect(res.status).toBe(201)
})Testing with Environment Variables
Pass environment variables as the third parameter:
const MOCK_ENV = {
API_HOST: 'example.com',
DB: {
prepare: () => {
/* mocked D1 */
},
},
}
test('GET /posts', async () => {
const res = await app.request('/posts', {}, MOCK_ENV)
expect(res.status).toBe(200)
})Testing Middleware
Test middleware behavior:
import { basicAuth } from 'hono/basic-auth'
const app = new Hono()
app.use(
'/admin/*',
basicAuth({
username: 'admin',
password: 'secret',
})
)
app.get('/admin', (c) => c.text('Admin panel'))
test('Unauthorized access', async () => {
const res = await app.request('/admin')
expect(res.status).toBe(401)
})
test('Authorized access', async () => {
const res = await app.request('/admin', {
headers: {
Authorization: 'Basic ' + btoa('admin:secret'),
},
})
expect(res.status).toBe(200)
expect(await res.text()).toBe('Admin panel')
})Runtime-Specific Testing
Cloudflare Workers
Use @cloudflare/vitest-pool-workers:
npm install -D @cloudflare/vitest-pool-workersSee Cloudflare’s Vitest integration guide .
Deno
Use Deno’s built-in test runner:
import { assertEquals } from '@std/assert'
import { Hono } from 'hono'
Deno.test('GET /posts', async () => {
const app = new Hono()
app.get('/posts', (c) => c.text('Many posts'))
const res = await app.request('http://localhost/posts')
assertEquals(res.status, 200)
assertEquals(await res.text(), 'Many posts')
})Bun
Use bun:test:
import { describe, expect, it } from 'bun:test'
import { Hono } from 'hono'
const app = new Hono()
app.get('/posts', (c) => c.text('Many posts'))
describe('API tests', () => {
it('Should return posts', async () => {
const res = await app.request('/posts')
expect(res.status).toBe(200)
expect(await res.text()).toBe('Many posts')
})
})Testing Helper
For type-safe testing, use the testing helper:
import { testClient } from 'hono/testing'
const client = testClient(app)
const res = await client.posts.$get()
expect(res.status).toBe(200)Next Steps
Explore Helpers and Built-in Middleware for more features.