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

> Learn how to test your Hono applications effectively.

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

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

```typescript theme={null}
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:

```typescript theme={null}
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:

```typescript theme={null}
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:

```typescript theme={null}
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):

```typescript theme={null}
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:

```typescript theme={null}
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:

```typescript theme={null}
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:

```typescript theme={null}
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:

```typescript theme={null}
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

<AccordionGroup>
  <Accordion title="Use testClient for type safety">
    The `testClient` helper provides full type safety and better developer experience.
  </Accordion>

  <Accordion title="Test edge cases">
    Test error conditions, invalid input, and edge cases, not just happy paths.
  </Accordion>

  <Accordion title="Mock external dependencies">
    Always mock external APIs and databases in unit tests.
  </Accordion>

  <Accordion title="Test middleware independently">
    Create focused tests for middleware before testing full request flows.
  </Accordion>

  <Accordion title="Use descriptive test names">
    Write clear test descriptions that explain what is being tested.
  </Accordion>
</AccordionGroup>

## Related

* [TypeScript](/guides/typescript) - Type-safe testing
* [Validation](/guides/validation) - Test validation logic
* [Error Handling](/guides/error-handling) - Test error scenarios
