Documentation Index Fetch the complete documentation index at: https://mintlify.com/honojs/hono/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Testing is crucial for building reliable applications. Hono provides excellent testing utilities that make it easy to test your routes, middleware, and handlers.
Test Client
The testClient helper allows you to test your Hono app with full type safety:
import { Hono } from 'hono'
import { testClient } from 'hono/testing'
const app = new Hono ()
. get ( '/posts/:id' , ( c ) => {
const id = c . req . param ( 'id' )
return c . json ({ id , title: 'Hello' })
})
. post ( '/posts' , async ( c ) => {
const body = await c . req . json ()
return c . json ({ id: '123' , ... body }, 201 )
})
// In your test file
const client = testClient ( app )
const res = await client . posts [ ':id' ]. $get ({
param: { id: '123' }
})
expect ( res . status ). toBe ( 200 )
const data = await res . json ()
expect ( data ). toEqual ({ id: '123' , title: 'Hello' })
Using app.request()
You can also test directly using app.request():
import { Hono } from 'hono'
const app = new Hono ()
app . get ( '/hello' , ( c ) => c . text ( 'Hello Hono!' ))
test ( 'GET /hello' , async () => {
const res = await app . request ( 'http://localhost/hello' )
expect ( res . status ). toBe ( 200 )
expect ( await res . text ()). toBe ( 'Hello Hono!' )
})
Testing with Vitest
Hono works great with Vitest:
import { describe , expect , it } from 'vitest'
import { Hono } from 'hono'
import { testClient } from 'hono/testing'
const app = new Hono ()
. get ( '/users/:id' , ( c ) => {
return c . json ({ id: c . req . param ( 'id' ) })
})
describe ( 'User API' , () => {
const client = testClient ( app )
it ( 'GET /users/:id - should return user' , async () => {
const res = await client . users [ ':id' ]. $get ({
param: { id: '123' }
})
expect ( res . status ). toBe ( 200 )
const data = await res . json ()
expect ( data ). toEqual ({ id: '123' })
})
})
Testing with Jest
Hono also works with Jest:
import { Hono } from 'hono'
const app = new Hono ()
app . post ( '/posts' , async ( c ) => {
const body = await c . req . json ()
return c . json ({ id: '1' , ... body }, 201 )
})
describe ( 'POST /posts' , () => {
it ( 'should create a post' , async () => {
const res = await app . request ( 'http://localhost/posts' , {
method: 'POST' ,
headers: {
'Content-Type' : 'application/json' ,
},
body: JSON . stringify ({
title: 'Hello' ,
body: 'World'
}),
})
expect ( res . status ). toBe ( 201 )
const data = await res . json ()
expect ( data ). toMatchObject ({
id: '1' ,
title: 'Hello' ,
body: 'World' ,
})
})
})
Testing Middleware
Test custom middleware:
import { Hono } from 'hono'
import type { MiddlewareHandler } from 'hono'
const authMiddleware : MiddlewareHandler = async ( c , next ) => {
const token = c . req . header ( 'Authorization' )
if ( ! token ) {
return c . text ( 'Unauthorized' , 401 )
}
await next ()
}
const app = new Hono ()
app . use ( '/admin/*' , authMiddleware )
app . get ( '/admin/dashboard' , ( c ) => c . text ( 'Dashboard' ))
test ( 'should require authentication' , async () => {
const res = await app . request ( 'http://localhost/admin/dashboard' )
expect ( res . status ). toBe ( 401 )
})
test ( 'should allow authenticated requests' , async () => {
const res = await app . request ( 'http://localhost/admin/dashboard' , {
headers: {
Authorization: 'Bearer token123' ,
},
})
expect ( res . status ). toBe ( 200 )
})
Testing with Environment Variables
Test with bindings (environment variables):
import { testClient } from 'hono/testing'
type Env = {
Bindings : {
API_KEY : string
DATABASE_URL : string
}
}
const app = new Hono < Env >()
app . get ( '/config' , ( c ) => {
return c . json ({
apiKey: c . env . API_KEY ,
dbUrl: c . env . DATABASE_URL ,
})
})
test ( 'should access environment variables' , async () => {
const mockEnv = {
API_KEY: 'test-key' ,
DATABASE_URL: 'test-db-url' ,
}
const client = testClient ( app , mockEnv )
const res = await client . config . $get ()
const data = await res . json ()
expect ( data ). toEqual ({
apiKey: 'test-key' ,
dbUrl: 'test-db-url' ,
})
})
Testing Validation
Test routes with validation:
import { Hono } from 'hono'
import { validator } from 'hono/validator'
const app = new Hono ()
app . post (
'/users' ,
validator ( 'json' , ( value , c ) => {
const data = value as { email : string }
if ( ! data . email || ! data . email . includes ( '@' )) {
return c . text ( 'Invalid email' , 400 )
}
return data
}),
( c ) => {
const data = c . req . valid ( 'json' )
return c . json ({ created: true , email: data . email })
}
)
test ( 'should validate email' , async () => {
const res = await app . request ( 'http://localhost/users' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({ email: 'invalid' }),
})
expect ( res . status ). toBe ( 400 )
expect ( await res . text ()). toBe ( 'Invalid email' )
})
test ( 'should accept valid email' , async () => {
const res = await app . request ( 'http://localhost/users' , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({ email: 'test@example.com' }),
})
expect ( res . status ). toBe ( 200 )
})
Testing Error Handling
Test error handlers:
import { Hono } from 'hono'
import { HTTPException } from 'hono/http-exception'
const app = new Hono ()
app . onError (( err , c ) => {
if ( err instanceof HTTPException ) {
return c . json ({ error: err . message }, err . status )
}
return c . json ({ error: 'Internal Server Error' }, 500 )
})
app . get ( '/error' , () => {
throw new HTTPException ( 400 , { message: 'Bad Request' })
})
test ( 'should handle errors' , async () => {
const res = await app . request ( 'http://localhost/error' )
expect ( res . status ). toBe ( 400 )
const data = await res . json ()
expect ( data ). toEqual ({ error: 'Bad Request' })
})
Testing Headers and Cookies
Test requests with headers and cookies:
import { Hono } from 'hono'
import { getCookie , setCookie } from 'hono/cookie'
const app = new Hono ()
app . get ( '/cookie' , ( c ) => {
const value = getCookie ( c , 'session' )
return c . text ( `Cookie: ${ value } ` )
})
app . post ( '/set-cookie' , ( c ) => {
setCookie ( c , 'session' , 'abc123' )
return c . text ( 'Cookie set' )
})
test ( 'should read cookies' , async () => {
const res = await app . request ( 'http://localhost/cookie' , {
headers: {
Cookie: 'session=test123' ,
},
})
expect ( await res . text ()). toBe ( 'Cookie: test123' )
})
test ( 'should set cookies' , async () => {
const res = await app . request ( 'http://localhost/set-cookie' , {
method: 'POST' ,
})
const setCookieHeader = res . headers . get ( 'Set-Cookie' )
expect ( setCookieHeader ). toContain ( 'session=abc123' )
})
Mocking External Services
Mock external API calls:
import { vi } from 'vitest'
import { Hono } from 'hono'
const fetchUser = async ( id : string ) => {
const res = await fetch ( `https://api.example.com/users/ ${ id } ` )
return res . json ()
}
const app = new Hono ()
app . get ( '/users/:id' , async ( c ) => {
const user = await fetchUser ( c . req . param ( 'id' ))
return c . json ( user )
})
test ( 'should fetch user data' , async () => {
// Mock the fetch function
global . fetch = vi . fn (). mockResolvedValue ({
json : async () => ({ id: '123' , name: 'John' }),
})
const res = await app . request ( 'http://localhost/users/123' )
const data = await res . json ()
expect ( data ). toEqual ({ id: '123' , name: 'John' })
expect ( fetch ). toHaveBeenCalledWith ( 'https://api.example.com/users/123' )
})
Best Practices
Use testClient for type safety
The testClient helper provides full type safety and better developer experience.
Test error conditions, invalid input, and edge cases, not just happy paths.
Mock external dependencies
Always mock external APIs and databases in unit tests.
Test middleware independently
Create focused tests for middleware before testing full request flows.
Use descriptive test names
Write clear test descriptions that explain what is being tested.