Skip to main content
doc0blade47/honodocumentation home
Sign in

Testing Hono Apps

Last updated Oct 12, 2026100% grounded

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.

  1. Import testClient from hono/testing.
  2. Pass your Hono app instance to testClient(app).
  3. 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.

Was this page helpful?