> ## 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.

# Testing Helper

> Utilities for testing Hono applications

The Testing Helper provides utilities for testing Hono applications with full type safety and a convenient API client.

## Import

```typescript theme={null}
import { testClient } from 'hono/testing'
```

## Functions

### testClient()

Creates a type-safe test client for making requests to your Hono application.

```typescript theme={null}
function testClient<T extends Hono<any, Schema, string>>(
  app: T,
  Env?: ExtractEnv<T>['Bindings'] | {},
  executionCtx?: ExecutionContext,
  options?: Omit<ClientRequestOptions, 'fetch'>
): UnionToIntersection<Client<T, 'http://localhost'>>
```

<ParamField path="app" type="Hono" required>
  The Hono application instance to test
</ParamField>

<ParamField path="Env" type="Bindings">
  Optional environment bindings (e.g., database, API keys)
</ParamField>

<ParamField path="executionCtx" type="ExecutionContext">
  Optional execution context for Cloudflare Workers
</ParamField>

<ParamField path="options" type="ClientRequestOptions">
  Additional client options (headers, etc.)
</ParamField>

<ResponseField name="return" type="Client">
  A fully typed client matching your app's routes
</ResponseField>

**Example**

```typescript theme={null}
import { Hono } from 'hono'
import { testClient } from 'hono/testing'
import { describe, it, expect } from 'vitest'

const app = new Hono()
  .get('/hello', (c) => c.text('Hello World'))
  .post('/posts', async (c) => {
    const { title } = await c.req.json()
    return c.json({ id: 1, title }, 201)
  })

describe('App', () => {
  it('GET /hello', async () => {
    const client = testClient(app)
    const res = await client.hello.$get()
    
    expect(res.status).toBe(200)
    expect(await res.text()).toBe('Hello World')
  })
  
  it('POST /posts', async () => {
    const client = testClient(app)
    const res = await client.posts.$post({
      json: { title: 'New Post' }
    })
    
    expect(res.status).toBe(201)
    const data = await res.json()
    expect(data.title).toBe('New Post')
  })
})
```

## Testing with Environment Bindings

Test applications that use environment variables:

```typescript theme={null}
import { testClient } from 'hono/testing'

type Env = {
  Bindings: {
    DB: Database
    API_KEY: string
  }
}

const app = new Hono<Env>()

app.get('/data', async (c) => {
  const data = await c.env.DB.query('SELECT * FROM users')
  return c.json(data)
})

describe('App with bindings', () => {
  it('should access database', async () => {
    const mockDB = {
      query: vi.fn().mockResolvedValue([{ id: 1, name: 'Alice' }])
    }
    
    const client = testClient(app, {
      DB: mockDB,
      API_KEY: 'test-key'
    })
    
    const res = await client.data.$get()
    const data = await res.json()
    
    expect(mockDB.query).toHaveBeenCalled()
    expect(data).toEqual([{ id: 1, name: 'Alice' }])
  })
})
```

## Testing with Variables

Test applications that use context variables:

```typescript theme={null}
import { testClient } from 'hono/testing'

type Env = {
  Variables: {
    user: { id: string; name: string }
  }
}

const app = new Hono<Env>()

app.use('*', async (c, next) => {
  c.set('user', { id: '1', name: 'Alice' })
  await next()
})

app.get('/profile', (c) => {
  const user = c.get('user')
  return c.json(user)
})

describe('App with variables', () => {
  it('should return user profile', async () => {
    const client = testClient(app)
    const res = await client.profile.$get()
    const user = await res.json()
    
    expect(user.name).toBe('Alice')
  })
})
```

## Type-Safe Request/Response

The test client provides full type safety:

```typescript theme={null}
import { testClient } from 'hono/testing'
import { z } from 'zod'
import { zValidator } from '@hono/zod-validator'

const schema = z.object({
  name: z.string(),
  age: z.number()
})

const app = new Hono()
  .post('/users', zValidator('json', schema), (c) => {
    const data = c.req.valid('json')
    return c.json({ id: 1, ...data })
  })

describe('Type-safe testing', () => {
  it('should validate types', async () => {
    const client = testClient(app)
    
    // ✓ TypeScript ensures correct types
    const res = await client.users.$post({
      json: {
        name: 'Alice',
        age: 30
      }
    })
    
    const data = await res.json()
    // data is typed as { id: number, name: string, age: number }
    expect(data.id).toBe(1)
  })
})
```

## Testing Different HTTP Methods

```typescript theme={null}
import { testClient } from 'hono/testing'

const app = new Hono()
  .get('/posts/:id', (c) => {
    return c.json({ id: c.req.param('id'), title: 'Post' })
  })
  .put('/posts/:id', async (c) => {
    const data = await c.req.json()
    return c.json({ id: c.req.param('id'), ...data })
  })
  .delete('/posts/:id', (c) => {
    return c.json({ deleted: true })
  })

describe('HTTP methods', () => {
  const client = testClient(app)
  
  it('GET with params', async () => {
    const res = await client.posts[':id'].$get({
      param: { id: '123' }
    })
    const data = await res.json()
    expect(data.id).toBe('123')
  })
  
  it('PUT with body', async () => {
    const res = await client.posts[':id'].$put({
      param: { id: '123' },
      json: { title: 'Updated' }
    })
    const data = await res.json()
    expect(data.title).toBe('Updated')
  })
  
  it('DELETE', async () => {
    const res = await client.posts[':id'].$delete({
      param: { id: '123' }
    })
    const data = await res.json()
    expect(data.deleted).toBe(true)
  })
})
```

## Testing Headers and Cookies

```typescript theme={null}
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({ message: 'Protected data' })
  })

describe('Headers', () => {
  it('should send authorization header', async () => {
    const client = testClient(app, {}, undefined, {
      headers: {
        Authorization: 'Bearer token123'
      }
    })
    
    const res = await client.protected.$get()
    expect(res.status).toBe(200)
  })
  
  it('should handle missing auth', async () => {
    const client = testClient(app)
    const res = await client.protected.$get()
    expect(res.status).toBe(401)
  })
})
```

## Testing with Query Parameters

```typescript theme={null}
import { testClient } from 'hono/testing'

const app = new Hono()
  .get('/search', (c) => {
    const query = c.req.query('q')
    const page = c.req.query('page') || '1'
    return c.json({ query, page })
  })

describe('Query parameters', () => {
  it('should handle query params', async () => {
    const client = testClient(app)
    const res = await client.search.$get({
      query: {
        q: 'test',
        page: '2'
      }
    })
    
    const data = await res.json()
    expect(data.query).toBe('test')
    expect(data.page).toBe('2')
  })
})
```

## Testing Nested Routes

```typescript theme={null}
import { testClient } from 'hono/testing'

const api = new Hono()
  .get('/users', (c) => c.json([]))
  .get('/posts', (c) => c.json([]))

const app = new Hono()
  .route('/api', api)

describe('Nested routes', () => {
  it('should access nested routes', async () => {
    const client = testClient(app)
    
    const usersRes = await client.api.users.$get()
    expect(usersRes.status).toBe(200)
    
    const postsRes = await client.api.posts.$get()
    expect(postsRes.status).toBe(200)
  })
})
```

## Complete Test Example

```typescript theme={null}
import { Hono } from 'hono'
import { testClient } from 'hono/testing'
import { describe, it, expect, beforeEach } from 'vitest'

type Env = {
  Bindings: {
    DB: Database
  }
}

const app = new Hono<Env>()

app.get('/health', (c) => c.json({ status: 'ok' }))

app.post('/users', async (c) => {
  const { name, email } = await c.req.json()
  const user = await c.env.DB.createUser({ name, email })
  return c.json(user, 201)
})

app.get('/users/:id', async (c) => {
  const id = c.req.param('id')
  const user = await c.env.DB.getUser(id)
  if (!user) {
    return c.notFound()
  }
  return c.json(user)
})

describe('User API', () => {
  let mockDB: any
  
  beforeEach(() => {
    mockDB = {
      createUser: vi.fn(),
      getUser: vi.fn()
    }
  })
  
  it('should return health status', async () => {
    const client = testClient(app, { DB: mockDB })
    const res = await client.health.$get()
    
    expect(res.status).toBe(200)
    expect(await res.json()).toEqual({ status: 'ok' })
  })
  
  it('should create user', async () => {
    mockDB.createUser.mockResolvedValue({
      id: '1',
      name: 'Alice',
      email: 'alice@example.com'
    })
    
    const client = testClient(app, { DB: mockDB })
    const res = await client.users.$post({
      json: {
        name: 'Alice',
        email: 'alice@example.com'
      }
    })
    
    expect(res.status).toBe(201)
    expect(mockDB.createUser).toHaveBeenCalledWith({
      name: 'Alice',
      email: 'alice@example.com'
    })
  })
  
  it('should get user by id', async () => {
    mockDB.getUser.mockResolvedValue({
      id: '1',
      name: 'Alice',
      email: 'alice@example.com'
    })
    
    const client = testClient(app, { DB: mockDB })
    const res = await client.users[':id'].$get({
      param: { id: '1' }
    })
    
    expect(res.status).toBe(200)
    const user = await res.json()
    expect(user.id).toBe('1')
  })
  
  it('should return 404 for non-existent user', async () => {
    mockDB.getUser.mockResolvedValue(null)
    
    const client = testClient(app, { DB: mockDB })
    const res = await client.users[':id'].$get({
      param: { id: '999' }
    })
    
    expect(res.status).toBe(404)
  })
})
```
