The Testing helper provides utilities for testing your Hono applications with type-safe request methods.
Import
import { testClient } from 'hono/testing'
Basic Usage
Create a type-safe client for testing your app:
import { Hono } from 'hono'
import { testClient } from 'hono/testing'
const app = new Hono ()
. get ( '/posts' , ( c ) => c . json ({ posts: [] }))
. post ( '/posts' , ( c ) => c . json ({ id: 1 }, 201 ))
const client = testClient ( app )
// Make requests
const res = await client . posts . $get ()
expect ( res . status ). toBe ( 200 )
const data = await res . json ()
expect ( data ). toEqual ({ posts: [] })
Features
Type-Safe Requests
The test client provides full TypeScript support:
import { Hono } from 'hono'
import { testClient } from 'hono/testing'
const app = new Hono ()
. get ( '/users/:id' , ( c ) => {
const id = c . req . param ( 'id' )
return c . json ({ id , name: 'John' })
})
. post ( '/users' , async ( c ) => {
const body = await c . req . json ()
return c . json ({ id: 1 , ... body }, 201 )
})
const client = testClient ( app )
// Type-safe requests
const getRes = await client . users [ ':id' ]. $get ({
param: { id: '123' }
})
const postRes = await client . users . $post ({
json: { name: 'Jane' }
})
Query Parameters
Test routes with query parameters:
import { Hono } from 'hono'
import { testClient } from 'hono/testing'
const app = new Hono ()
. get ( '/search' , ( c ) => {
const query = c . req . query ( 'q' )
return c . json ({ query })
})
const client = testClient ( app )
const res = await client . search . $get ({
query: { q: 'hono' }
})
const data = await res . json ()
expect ( data . query ). toBe ( 'hono' )
Include custom headers in requests:
import { Hono } from 'hono'
import { testClient } from 'hono/testing'
const app = new Hono ()
. get ( '/protected' , ( c ) => {
const auth = c . req . header ( 'Authorization' )
if ( ! auth ) return c . text ( 'Unauthorized' , 401 )
return c . json ({ data: 'secret' })
})
const client = testClient ( app )
const res = await client . protected . $get ({
headers: {
Authorization: 'Bearer token123'
}
})
expect ( res . status ). toBe ( 200 )
JSON Request Body
Send JSON data in requests:
import { Hono } from 'hono'
import { testClient } from 'hono/testing'
const app = new Hono ()
. post ( '/api/data' , async ( c ) => {
const body = await c . req . json <{ name : string ; age : number }>()
return c . json ({ received: body })
})
const client = testClient ( app )
const res = await client . api . data . $post ({
json: { name: 'Alice' , age: 30 }
})
const data = await res . json ()
expect ( data . received . name ). toBe ( 'Alice' )
Test file uploads and form submissions:
import { Hono } from 'hono'
import { testClient } from 'hono/testing'
const app = new Hono ()
. post ( '/upload' , async ( c ) => {
const body = await c . req . parseBody ()
return c . json ({ filename: body . file ?. name })
})
const client = testClient ( app )
const formData = new FormData ()
formData . append ( 'file' , new Blob ([ 'content' ]), 'test.txt' )
const res = await client . upload . $post ({
body: formData
})
Environment Variables
Test with environment bindings:
import { Hono } from 'hono'
import { testClient } from 'hono/testing'
type Bindings = {
API_KEY : string
DATABASE_URL : string
}
const app = new Hono <{ Bindings : Bindings }>()
. get ( '/config' , ( c ) => {
return c . json ({
apiKey: c . env . API_KEY ,
dbUrl: c . env . DATABASE_URL
})
})
const client = testClient ( app , {
API_KEY: 'test-key' ,
DATABASE_URL: 'postgres://localhost'
})
const res = await client . config . $get ()
const data = await res . json ()
expect ( data . apiKey ). toBe ( 'test-key' )
Execution Context
Provide execution context for testing:
import { testClient } from 'hono/testing'
import type { ExecutionContext } from 'hono'
const mockContext : ExecutionContext = {
waitUntil : ( promise : Promise < any >) => {},
passThroughOnException : () => {},
}
const client = testClient (
app ,
undefined , // Bindings
mockContext
)
Client Options
Customize the test client:
import { testClient } from 'hono/testing'
const client = testClient ( app , undefined , undefined , {
headers: {
'User-Agent' : 'test-client' ,
'Accept-Language' : 'en-US'
}
})
// All requests will include these headers
const res = await client . api . $get ()
Testing Patterns
Unit Tests
Test individual routes:
import { describe , it , expect } from 'vitest'
import { testClient } from 'hono/testing'
describe ( 'API Routes' , () => {
it ( 'should return user list' , async () => {
const client = testClient ( app )
const res = await client . users . $get ()
expect ( res . status ). toBe ( 200 )
const data = await res . json ()
expect ( Array . isArray ( data . users )). toBe ( true )
})
it ( 'should create a user' , async () => {
const client = testClient ( app )
const res = await client . users . $post ({
json: { name: 'Test User' }
})
expect ( res . status ). toBe ( 201 )
const data = await res . json ()
expect ( data . name ). toBe ( 'Test User' )
})
})
Integration Tests
Test complete workflows:
import { describe , it , expect } from 'vitest'
import { testClient } from 'hono/testing'
describe ( 'User workflow' , () => {
it ( 'should create and retrieve user' , async () => {
const client = testClient ( app )
// Create user
const createRes = await client . users . $post ({
json: { name: 'Alice' , email: '[email protected] ' }
})
expect ( createRes . status ). toBe ( 201 )
const created = await createRes . json ()
// Retrieve user
const getRes = await client . users [ ':id' ]. $get ({
param: { id: created . id }
})
expect ( getRes . status ). toBe ( 200 )
const retrieved = await getRes . json ()
expect ( retrieved . name ). toBe ( 'Alice' )
})
})
Testing Middleware
Test middleware behavior:
import { Hono } from 'hono'
import { testClient } from 'hono/testing'
import { logger } from 'hono/logger'
import { cors } from 'hono/cors'
const app = new Hono ()
. use ( '*' , logger ())
. use ( '*' , cors ())
. get ( '/api' , ( c ) => c . json ({ ok: true }))
const client = testClient ( app )
const res = await client . api . $get ()
expect ( res . headers . get ( 'Access-Control-Allow-Origin' )). toBe ( '*' )
Testing Error Cases
Test error handling:
import { Hono } from 'hono'
import { testClient } from 'hono/testing'
const app = new Hono ()
. get ( '/error' , ( c ) => {
throw new Error ( 'Something went wrong' )
})
. onError (( err , c ) => {
return c . json ({ error: err . message }, 500 )
})
const client = testClient ( app )
const res = await client . error . $get ()
expect ( res . status ). toBe ( 500 )
const data = await res . json ()
expect ( data . error ). toBe ( 'Something went wrong' )
Testing with Different Test Frameworks
Vitest
import { describe , it , expect , beforeEach } from 'vitest'
import { testClient } from 'hono/testing'
import { app } from './app'
describe ( 'API' , () => {
let client : ReturnType < typeof testClient >
beforeEach (() => {
client = testClient ( app )
})
it ( 'works' , async () => {
const res = await client . index . $get ()
expect ( res . status ). toBe ( 200 )
})
})
Jest
import { testClient } from 'hono/testing'
import { app } from './app'
describe ( 'API' , () => {
let client : ReturnType < typeof testClient >
beforeEach (() => {
client = testClient ( app )
})
test ( 'returns 200' , async () => {
const res = await client . index . $get ()
expect ( res . status ). toBe ( 200 )
})
})
Node Test Runner
import { describe , it } from 'node:test'
import assert from 'node:assert'
import { testClient } from 'hono/testing'
import { app } from './app'
describe ( 'API' , () => {
it ( 'returns 200' , async () => {
const client = testClient ( app )
const res = await client . index . $get ()
assert . strictEqual ( res . status , 200 )
})
})
URL Generation
Generate URLs for routes:
import { testClient } from 'hono/testing'
const client = testClient ( app )
const url = client . users [ ':id' ]. $url ({
param: { id: '123' },
query: { include: 'posts' }
})
console . log ( url . pathname ) // /users/123
console . log ( url . searchParams . get ( 'include' )) // posts
Mock Responses
Mock external dependencies:
import { Hono } from 'hono'
import { testClient } from 'hono/testing'
const app = new Hono ()
. get ( '/external' , async ( c ) => {
const res = await fetch ( 'https://api.example.com/data' )
const data = await res . json ()
return c . json ( data )
})
// Mock fetch
globalThis . fetch = async () => {
return new Response ( JSON . stringify ({ mocked: true }))
}
const client = testClient ( app )
const res = await client . external . $get ()
const data = await res . json ()
expect ( data . mocked ). toBe ( true )
Best Practices
Type Safety Leverage TypeScript for compile-time type checking of requests
Isolate Tests Create a new test client for each test to avoid side effects
Test Real Paths Test actual user paths through your application
Mock External APIs Mock external dependencies for faster, reliable tests
The test client uses the same Hono client (hc) used in production, ensuring your tests reflect real usage.
Test clients run against your Hono app directly without HTTP overhead, making tests fast and reliable.