Overview
Testing Hono applications is straightforward thanks to built-in helper methods and standard Web Standards Request and Response objects. Hono provides the .request() method on application instances for simple endpoint testing, as well as the testClient helper for end-to-end typed client testing.
Note
Hono runs on Web Standards, meaning standard testing frameworks like Vitest, Bun Test, or Deno Test work out of the box without complex server setup or port binding.
Using the Built-in .request() Method
The .request() method is built directly into every Hono application instance. It lets you simulate incoming HTTP requests by passing a URL path, a full URL, or a standard Request object, returning a standard Response promise.
import { Hono } from 'hono'
const app = new Hono()
app.get('/hello', (c) => c.text('Hello Hono!'))
// Inside your test file (e.g., using Vitest or Bun Test)
test('GET /hello returns correct text', async () => {
const res = await app.request('/hello')
expect(res.status).toBe(200)
expect(await res.text()).toBe('Hello Hono!')
})You can pass additional request options such as headers, methods, and bodies using a standard RequestInit object as the second argument:
test('POST /echo returns JSON body', async () => {
const res = await app.request('/echo', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ message: 'test' }),
})
expect(res.status).toBe(200)
const data = await res.json()
expect(data).toEqual({ message: 'test' })
})Passing Environment Bindings and Context
When testing apps that rely on environment variables, secrets, or platform bindings (such as Cloudflare Workers KV or D1 databases), you can pass bindings and execution context directly into .request().
const app = new Hono<{ Bindings: { SECRET_KEY: string } }>()
app.get('/secret', (c) => {
return c.text(c.env.SECRET_KEY)
})
test('GET /secret reads environment bindings', async () => {
// Pass Env bindings as the third argument
const res = await app.request('/secret', {}, { SECRET_KEY: 'my-super-secret' })
expect(await res.text()).toBe('my-super-secret')
})Using the testClient Helper
Getting Started with testClient
Hono provides a specialized testClient helper function that wraps your app in a fully typed RPC client. This allows you to invoke your endpoints programmatically with strict TypeScript type checking on both request inputs and response payloads.
- Import
testClientfromhono/testing. - Pass your Hono app instance to
testClient(app). - Call your routes using the intuitive method-chaining syntax.
import { Hono } from 'hono'
import { testClient } from 'hono/testing'
const app = new Hono().get('/user/:id', (c) => {
const id = c.req.param('id')
return c.json({ id, name: 'Alice' })
})
test('GET /user/:id via testClient', async () => {
const client = testClient(app)
const res = await client.user[':id'].$get({
param: { id: '123' },
})
expect(res.status).toBe(200)
const data = await res.json()
expect(data).toEqual({ id: '123', name: 'Alice' })
})Tip
testClient accepts environment bindings and execution contexts as its second and third optional arguments, making it easy to test authenticated or bound routes without spinning up a real HTTP server.
Tips and Warnings
Tip
Keep your test suites fast by testing your Hono app instances directly with .request() or testClient instead of starting a physical network listener.
Warning
Remember to await responses when calling asynchronous methods like .json() or .text() on the returned Response object.