hono-testing

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Hono Testing Patterns

Hono 测试模式

Overview

概述

Hono provides a simple testing approach: create a Request, pass it to your app, and validate the Response. The framework includes a typed test client for even better DX.
Key Features:
  • Simple
    app.request()
    API
  • Typed test client with full inference
  • Environment mocking for Workers
  • Works with Vitest, Jest, or any test runner
Hono 提供了一种简单的测试方法:创建一个Request,将其传入你的应用,然后验证Response。该框架包含一个类型化测试客户端,以提供更出色的开发体验(DX)。
核心特性:
  • 简单的
    app.request()
    API
  • 具备完整类型推断的类型化测试客户端
  • 针对Workers的环境模拟
  • 可与Vitest、Jest或任何测试运行器配合使用

When to Use This Skill

适用场景

Use Hono testing when:
  • Writing unit tests for route handlers
  • Integration testing API endpoints
  • Testing middleware behavior
  • Mocking Cloudflare Workers bindings
  • Validating request/response cycles
在以下场景中使用Hono测试:
  • 为路由处理器编写单元测试
  • 对API端点进行集成测试
  • 测试中间件行为
  • 模拟Cloudflare Workers绑定
  • 验证请求/响应周期

Basic Testing

基础测试

Using app.request()

使用app.request()

typescript
import { Hono } from 'hono'
import { describe, it, expect } from 'vitest'

const app = new Hono()

app.get('/hello', (c) => c.text('Hello!'))
app.get('/json', (c) => c.json({ message: 'Hello' }))

describe('Basic routes', () => {
  it('should return text', async () => {
    const res = await app.request('/hello')

    expect(res.status).toBe(200)
    expect(await res.text()).toBe('Hello!')
  })

  it('should return JSON', async () => {
    const res = await app.request('/json')

    expect(res.status).toBe(200)
    expect(res.headers.get('Content-Type')).toContain('application/json')
    expect(await res.json()).toEqual({ message: 'Hello' })
  })
})
typescript
import { Hono } from 'hono'
import { describe, it, expect } from 'vitest'

const app = new Hono()

app.get('/hello', (c) => c.text('Hello!'))
app.get('/json', (c) => c.json({ message: 'Hello' }))

describe('Basic routes', () => {
  it('should return text', async () => {
    const res = await app.request('/hello')

    expect(res.status).toBe(200)
    expect(await res.text()).toBe('Hello!')
  })

  it('should return JSON', async () => {
    const res = await app.request('/json')

    expect(res.status).toBe(200)
    expect(res.headers.get('Content-Type')).toContain('application/json')
    expect(await res.json()).toEqual({ message: 'Hello' })
  })
})

Request Options

请求选项

typescript
// GET with query params
const res = await app.request('/search?q=hono&page=1')

// POST with JSON body
const res = await app.request('/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ name: 'Alice', email: 'alice@example.com' })
})

// POST with form data
const formData = new FormData()
formData.append('name', 'Alice')
formData.append('email', 'alice@example.com')

const res = await app.request('/users', {
  method: 'POST',
  body: formData
})

// With custom headers
const res = await app.request('/protected', {
  headers: {
    'Authorization': 'Bearer token123',
    'X-Custom-Header': 'value'
  }
})

// DELETE request
const res = await app.request('/users/123', {
  method: 'DELETE'
})
typescript
// GET 请求带查询参数
const res = await app.request('/search?q=hono&page=1')

// POST 请求带JSON体
const res = await app.request('/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ name: 'Alice', email: 'alice@example.com' })
})

// POST 请求带表单数据
const formData = new FormData()
formData.append('name', 'Alice')
formData.append('email', 'alice@example.com')

const res = await app.request('/users', {
  method: 'POST',
  body: formData
})

// 自定义请求头
const res = await app.request('/protected', {
  headers: {
    'Authorization': 'Bearer token123',
    'X-Custom-Header': 'value'
  }
})

// DELETE 请求
const res = await app.request('/users/123', {
  method: 'DELETE'
})

Typed Test Client

类型化测试客户端

The test client provides full type inference:
typescript
import { Hono } from 'hono'
import { testClient } from 'hono/testing'
import { describe, it, expect } from 'vitest'

const app = new Hono()
  .get('/users', (c) => c.json({ users: [] }))
  .post('/users', async (c) => {
    const body = await c.req.json()
    return c.json({ id: '1', ...body }, 201)
  })
  .get('/users/:id', (c) => {
    return c.json({ id: c.req.param('id'), name: 'Alice' })
  })

describe('Users API', () => {
  const client = testClient(app)

  it('should list users', async () => {
    const res = await client.users.$get()

    expect(res.status).toBe(200)
    const data = await res.json()
    expect(data.users).toEqual([])
  })

  it('should create user', async () => {
    const res = await client.users.$post({
      json: { name: 'Alice', email: 'alice@example.com' }
    })

    expect(res.status).toBe(201)
    const data = await res.json()
    expect(data.name).toBe('Alice')
  })

  it('should get user by id', async () => {
    const res = await client.users[':id'].$get({
      param: { id: '123' }
    })

    expect(res.status).toBe(200)
    const data = await res.json()
    expect(data.id).toBe('123')
  })
})
测试客户端提供完整的类型推断:
typescript
import { Hono } from 'hono'
import { testClient } from 'hono/testing'
import { describe, it, expect } from 'vitest'

const app = new Hono()
  .get('/users', (c) => c.json({ users: [] }))
  .post('/users', async (c) => {
    const body = await c.req.json()
    return c.json({ id: '1', ...body }, 201)
  })
  .get('/users/:id', (c) => {
    return c.json({ id: c.req.param('id'), name: 'Alice' })
  })

describe('Users API', () => {
  const client = testClient(app)

  it('should list users', async () => {
    const res = await client.users.$get()

    expect(res.status).toBe(200)
    const data = await res.json()
    expect(data.users).toEqual([])
  })

  it('should create user', async () => {
    const res = await client.users.$post({
      json: { name: 'Alice', email: 'alice@example.com' }
    })

    expect(res.status).toBe(201)
    const data = await res.json()
    expect(data.name).toBe('Alice')
  })

  it('should get user by id', async () => {
    const res = await client.users[':id'].$get({
      param: { id: '123' }
    })

    expect(res.status).toBe(200)
    const data = await res.json()
    expect(data.id).toBe('123')
  })
})

Testing with Validation

带验证的测试

typescript
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'

const app = new Hono()

const createUserSchema = z.object({
  name: z.string().min(1),
  email: z.string().email()
})

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

describe('Validation', () => {
  it('should accept valid data', async () => {
    const res = await app.request('/users', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        name: 'Alice',
        email: 'alice@example.com'
      })
    })

    expect(res.status).toBe(201)
  })

  it('should reject invalid email', async () => {
    const res = await app.request('/users', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        name: 'Alice',
        email: 'invalid-email'
      })
    })

    expect(res.status).toBe(400)
  })

  it('should reject missing name', async () => {
    const res = await app.request('/users', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        email: 'alice@example.com'
      })
    })

    expect(res.status).toBe(400)
  })
})
typescript
import { Hono } from 'hono'
import { zValidator } from '@hono/zod-validator'
import { z } from 'zod'

const app = new Hono()

const createUserSchema = z.object({
  name: z.string().min(1),
  email: z.string().email()
})

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

describe('Validation', () => {
  it('should accept valid data', async () => {
    const res = await app.request('/users', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        name: 'Alice',
        email: 'alice@example.com'
      })
    })

    expect(res.status).toBe(201)
  })

  it('should reject invalid email', async () => {
    const res = await app.request('/users', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        name: 'Alice',
        email: 'invalid-email'
      })
    })

    expect(res.status).toBe(400)
  })

  it('should reject missing name', async () => {
    const res = await app.request('/users', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        email: 'alice@example.com'
      })
    })

    expect(res.status).toBe(400)
  })
})

Mocking Environment (Cloudflare Workers)

模拟环境(Cloudflare Workers)

Mock Bindings

模拟绑定

typescript
import { Hono } from 'hono'

type Bindings = {
  DB: D1Database
  KV: KVNamespace
  API_KEY: string
}

const app = new Hono<{ Bindings: Bindings }>()

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

app.get('/config', (c) => {
  return c.json({ apiKey: c.env.API_KEY.slice(0, 4) + '...' })
})

describe('With mocked bindings', () => {
  // Mock D1 database
  const mockDB = {
    prepare: () => ({
      all: async () => ({ results: [{ id: 1, name: 'Alice' }] }),
      first: async () => ({ id: 1, name: 'Alice' }),
      run: async () => ({ success: true })
    })
  }

  // Mock KV namespace
  const mockKV = {
    get: async (key: string) => 'cached-value',
    put: async (key: string, value: string) => {},
    delete: async (key: string) => {}
  }

  const mockEnv: Bindings = {
    DB: mockDB as unknown as D1Database,
    KV: mockKV as unknown as KVNamespace,
    API_KEY: 'test-api-key-12345'  // pragma: allowlist secret
  }

  it('should use mocked database', async () => {
    const res = await app.request('/data', {}, mockEnv)

    expect(res.status).toBe(200)
    const data = await res.json()
    expect(data.results).toHaveLength(1)
  })

  it('should use mocked API key', async () => {
    const res = await app.request('/config', {}, mockEnv)

    const data = await res.json()
    expect(data.apiKey).toBe('test...')
  })
})
typescript
import { Hono } from 'hono'

type Bindings = {
  DB: D1Database
  KV: KVNamespace
  API_KEY: string
}

const app = new Hono<{ Bindings: Bindings }>()

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

app.get('/config', (c) => {
  return c.json({ apiKey: c.env.API_KEY.slice(0, 4) + '...' })
})

describe('With mocked bindings', () => {
  // 模拟D1数据库
  const mockDB = {
    prepare: () => ({
      all: async () => ({ results: [{ id: 1, name: 'Alice' }] }),
      first: async () => ({ id: 1, name: 'Alice' }),
      run: async () => ({ success: true })
    })
  }

  // 模拟KV命名空间
  const mockKV = {
    get: async (key: string) => 'cached-value',
    put: async (key: string, value: string) => {},
    delete: async (key: string) => {}
  }

  const mockEnv: Bindings = {
    DB: mockDB as unknown as D1Database,
    KV: mockKV as unknown as KVNamespace,
    API_KEY: 'test-api-key-12345'  // pragma: allowlist secret
  }

  it('should use mocked database', async () => {
    const res = await app.request('/data', {}, mockEnv)

    expect(res.status).toBe(200)
    const data = await res.json()
    expect(data.results).toHaveLength(1)
  })

  it('should use mocked API key', async () => {
    const res = await app.request('/config', {}, mockEnv)

    const data = await res.json()
    expect(data.apiKey).toBe('test...')
  })
})

Using Miniflare

使用Miniflare

For more realistic Cloudflare Workers testing:
typescript
import { Miniflare } from 'miniflare'
import { describe, it, expect, beforeAll, afterAll } from 'vitest'

describe('With Miniflare', () => {
  let mf: Miniflare

  beforeAll(async () => {
    mf = new Miniflare({
      script: `
        import app from './src/index'
        export default app
      `,
      modules: true,
      d1Databases: ['DB'],
      kvNamespaces: ['KV']
    })
  })

  afterAll(async () => {
    await mf.dispose()
  })

  it('should work with real bindings', async () => {
    const res = await mf.dispatchFetch('http://localhost/data')
    expect(res.status).toBe(200)
  })
})
如需更真实的Cloudflare Workers测试:
typescript
import { Miniflare } from 'miniflare'
import { describe, it, expect, beforeAll, afterAll } from 'vitest'

describe('With Miniflare', () => {
  let mf: Miniflare

  beforeAll(async () => {
    mf = new Miniflare({
      script: `
        import app from './src/index'
        export default app
      `,
      modules: true,
      d1Databases: ['DB'],
      kvNamespaces: ['KV']
    })
  })

  afterAll(async () => {
    await mf.dispose()
  })

  it('should work with real bindings', async () => {
    const res = await mf.dispatchFetch('http://localhost/data')
    expect(res.status).toBe(200)
  })
})

Testing Middleware

测试中间件

typescript
import { Hono } from 'hono'
import { createMiddleware } from 'hono/factory'

// Middleware to test
const authMiddleware = createMiddleware(async (c, next) => {
  const token = c.req.header('Authorization')?.replace('Bearer ', '')

  if (!token) {
    return c.json({ error: 'Unauthorized' }, 401)
  }

  if (token !== 'valid-token') {
    return c.json({ error: 'Invalid token' }, 403)
  }

  c.set('userId', 'user-123')
  await next()
})

const app = new Hono()

app.use('/protected/*', authMiddleware)

app.get('/protected/data', (c) => {
  const userId = c.get('userId')
  return c.json({ userId, data: 'secret' })
})

describe('Auth middleware', () => {
  it('should reject request without token', async () => {
    const res = await app.request('/protected/data')

    expect(res.status).toBe(401)
    expect(await res.json()).toEqual({ error: 'Unauthorized' })
  })

  it('should reject invalid token', async () => {
    const res = await app.request('/protected/data', {
      headers: { 'Authorization': 'Bearer invalid' }
    })

    expect(res.status).toBe(403)
    expect(await res.json()).toEqual({ error: 'Invalid token' })
  })

  it('should allow valid token', async () => {
    const res = await app.request('/protected/data', {
      headers: { 'Authorization': 'Bearer valid-token' }
    })

    expect(res.status).toBe(200)
    const data = await res.json()
    expect(data.userId).toBe('user-123')
  })
})
typescript
import { Hono } from 'hono'
import { createMiddleware } from 'hono/factory'

// 待测试的中间件
const authMiddleware = createMiddleware(async (c, next) => {
  const token = c.req.header('Authorization')?.replace('Bearer ', '')

  if (!token) {
    return c.json({ error: 'Unauthorized' }, 401)
  }

  if (token !== 'valid-token') {
    return c.json({ error: 'Invalid token' }, 403)
  }

  c.set('userId', 'user-123')
  await next()
})

const app = new Hono()

app.use('/protected/*', authMiddleware)

app.get('/protected/data', (c) => {
  const userId = c.get('userId')
  return c.json({ userId, data: 'secret' })
})

describe('Auth middleware', () => {
  it('should reject request without token', async () => {
    const res = await app.request('/protected/data')

    expect(res.status).toBe(401)
    expect(await res.json()).toEqual({ error: 'Unauthorized' })
  })

  it('should reject invalid token', async () => {
    const res = await app.request('/protected/data', {
      headers: { 'Authorization': 'Bearer invalid' }
    })

    expect(res.status).toBe(403)
    expect(await res.json()).toEqual({ error: 'Invalid token' })
  })

  it('should allow valid token', async () => {
    const res = await app.request('/protected/data', {
      headers: { 'Authorization': 'Bearer valid-token' }
    })

    expect(res.status).toBe(200)
    const data = await res.json()
    expect(data.userId).toBe('user-123')
  })
})

Testing Error Handling

测试错误处理

typescript
import { Hono } from 'hono'
import { HTTPException } from 'hono/http-exception'

const app = new Hono()

app.get('/error', () => {
  throw new HTTPException(500, { message: 'Something went wrong' })
})

app.get('/not-found', (c) => {
  return c.notFound()
})

app.onError((err, c) => {
  if (err instanceof HTTPException) {
    return c.json({ error: err.message }, err.status)
  }
  return c.json({ error: 'Internal error' }, 500)
})

app.notFound((c) => {
  return c.json({ error: 'Not found' }, 404)
})

describe('Error handling', () => {
  it('should handle HTTPException', async () => {
    const res = await app.request('/error')

    expect(res.status).toBe(500)
    expect(await res.json()).toEqual({ error: 'Something went wrong' })
  })

  it('should handle not found', async () => {
    const res = await app.request('/unknown-route')

    expect(res.status).toBe(404)
    expect(await res.json()).toEqual({ error: 'Not found' })
  })
})
typescript
import { Hono } from 'hono'
import { HTTPException } from 'hono/http-exception'

const app = new Hono()

app.get('/error', () => {
  throw new HTTPException(500, { message: 'Something went wrong' })
})

app.get('/not-found', (c) => {
  return c.notFound()
})

app.onError((err, c) => {
  if (err instanceof HTTPException) {
    return c.json({ error: err.message }, err.status)
  }
  return c.json({ error: 'Internal error' }, 500)
})

app.notFound((c) => {
  return c.json({ error: 'Not found' }, 404)
})

describe('Error handling', () => {
  it('should handle HTTPException', async () => {
    const res = await app.request('/error')

    expect(res.status).toBe(500)
    expect(await res.json()).toEqual({ error: 'Something went wrong' })
  })

  it('should handle not found', async () => {
    const res = await app.request('/unknown-route')

    expect(res.status).toBe(404)
    expect(await res.json()).toEqual({ error: 'Not found' })
  })
})

Testing File Uploads

测试文件上传

typescript
import { Hono } from 'hono'

const app = new Hono()

app.post('/upload', async (c) => {
  const formData = await c.req.formData()
  const file = formData.get('file') as File

  if (!file) {
    return c.json({ error: 'No file provided' }, 400)
  }

  return c.json({
    filename: file.name,
    size: file.size,
    type: file.type
  })
})

describe('File upload', () => {
  it('should handle file upload', async () => {
    const file = new File(['hello world'], 'test.txt', { type: 'text/plain' })
    const formData = new FormData()
    formData.append('file', file)

    const res = await app.request('/upload', {
      method: 'POST',
      body: formData
    })

    expect(res.status).toBe(200)
    const data = await res.json()
    expect(data.filename).toBe('test.txt')
    expect(data.size).toBe(11)
    expect(data.type).toBe('text/plain')
  })

  it('should reject missing file', async () => {
    const formData = new FormData()

    const res = await app.request('/upload', {
      method: 'POST',
      body: formData
    })

    expect(res.status).toBe(400)
  })
})
typescript
import { Hono } from 'hono'

const app = new Hono()

app.post('/upload', async (c) => {
  const formData = await c.req.formData()
  const file = formData.get('file') as File

  if (!file) {
    return c.json({ error: 'No file provided' }, 400)
  }

  return c.json({
    filename: file.name,
    size: file.size,
    type: file.type
  })
})

describe('File upload', () => {
  it('should handle file upload', async () => {
    const file = new File(['hello world'], 'test.txt', { type: 'text/plain' })
    const formData = new FormData()
    formData.append('file', file)

    const res = await app.request('/upload', {
      method: 'POST',
      body: formData
    })

    expect(res.status).toBe(200)
    const data = await res.json()
    expect(data.filename).toBe('test.txt')
    expect(data.size).toBe(11)
    expect(data.type).toBe('text/plain')
  })

  it('should reject missing file', async () => {
    const formData = new FormData()

    const res = await app.request('/upload', {
      method: 'POST',
      body: formData
    })

    expect(res.status).toBe(400)
  })
})

Test Setup Patterns

测试设置模式

Vitest Configuration

Vitest 配置

typescript
// vitest.config.ts
import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    globals: true,
    environment: 'node',
    coverage: {
      reporter: ['text', 'json', 'html'],
      exclude: ['node_modules/', 'dist/']
    }
  }
})
typescript
// vitest.config.ts
import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    globals: true,
    environment: 'node',
    coverage: {
      reporter: ['text', 'json', 'html'],
      exclude: ['node_modules/', 'dist/']
    }
  }
})

Test Utilities

测试工具

typescript
// test/utils.ts
import { Hono } from 'hono'
import type { Bindings } from '../src/types'

export function createTestApp() {
  // Return fresh app instance for each test
  return new Hono<{ Bindings: Bindings }>()
}

export function createMockEnv(overrides: Partial<Bindings> = {}): Bindings {
  return {
    DB: createMockDB(),
    KV: createMockKV(),
    API_KEY: 'test-key',  // pragma: allowlist secret
    ...overrides
  }
}

export function createMockDB() {
  return {
    prepare: (sql: string) => ({
      bind: (...args: any[]) => ({
        all: async () => ({ results: [] }),
        first: async () => null,
        run: async () => ({ success: true })
      }),
      all: async () => ({ results: [] }),
      first: async () => null,
      run: async () => ({ success: true })
    })
  }
}

export function createMockKV() {
  const store = new Map<string, string>()

  return {
    get: async (key: string) => store.get(key) ?? null,
    put: async (key: string, value: string) => { store.set(key, value) },
    delete: async (key: string) => { store.delete(key) }
  }
}
typescript
// test/utils.ts
import { Hono } from 'hono'
import type { Bindings } from '../src/types'

export function createTestApp() {
  // 为每个测试返回全新的应用实例
  return new Hono<{ Bindings: Bindings }>()
}

export function createMockEnv(overrides: Partial<Bindings> = {}): Bindings {
  return {
    DB: createMockDB(),
    KV: createMockKV(),
    API_KEY: 'test-key',  // pragma: allowlist secret
    ...overrides
  }
}

export function createMockDB() {
  return {
    prepare: (sql: string) => ({
      bind: (...args: any[]) => ({
        all: async () => ({ results: [] }),
        first: async () => null,
        run: async () => ({ success: true })
      }),
      all: async () => ({ results: [] }),
      first: async () => null,
      run: async () => ({ success: true })
    })
  }
}

export function createMockKV() {
  const store = new Map<string, string>()

  return {
    get: async (key: string) => store.get(key) ?? null,
    put: async (key: string, value: string) => { store.set(key, value) },
    delete: async (key: string) => { store.delete(key) }
  }
}

Using Test Utilities

使用测试工具

typescript
import { describe, it, expect, beforeEach } from 'vitest'
import { createTestApp, createMockEnv } from './utils'
import { setupRoutes } from '../src/routes'

describe('API Tests', () => {
  let app: ReturnType<typeof createTestApp>
  let env: ReturnType<typeof createMockEnv>

  beforeEach(() => {
    app = createTestApp()
    env = createMockEnv()
    setupRoutes(app)
  })

  it('should work with fresh instances', async () => {
    const res = await app.request('/api/health', {}, env)
    expect(res.status).toBe(200)
  })
})
typescript
import { describe, it, expect, beforeEach } from 'vitest'
import { createTestApp, createMockEnv } from './utils'
import { setupRoutes } from '../src/routes'

describe('API Tests', () => {
  let app: ReturnType<typeof createTestApp>
  let env: ReturnType<typeof createMockEnv>

  beforeEach(() => {
    app = createTestApp()
    env = createMockEnv()
    setupRoutes(app)
  })

  it('should work with fresh instances', async () => {
    const res = await app.request('/api/health', {}, env)
    expect(res.status).toBe(200)
  })
})

Quick Reference

快速参考

app.request() Signature

app.request() 签名

typescript
app.request(
  path: string,
  options?: RequestInit,
  env?: Bindings
): Promise<Response>
typescript
app.request(
  path: string,
  options?: RequestInit,
  env?: Bindings
): Promise<Response>

Common Assertions

常见断言

typescript
// Status
expect(res.status).toBe(200)
expect(res.ok).toBe(true)

// Headers
expect(res.headers.get('Content-Type')).toContain('application/json')
expect(res.headers.get('X-Custom')).toBe('value')

// Body
expect(await res.text()).toBe('Hello')
expect(await res.json()).toEqual({ key: 'value' })

// Response properties
expect(res.redirected).toBe(false)
expect(res.url).toBe('http://localhost/path')
typescript
// 状态码
expect(res.status).toBe(200)
expect(res.ok).toBe(true)

// 请求头
expect(res.headers.get('Content-Type')).toContain('application/json')
expect(res.headers.get('X-Custom')).toBe('value')

// 响应体
expect(await res.text()).toBe('Hello')
expect(await res.json()).toEqual({ key: 'value' })

// 响应属性
expect(res.redirected).toBe(false)
expect(res.url).toBe('http://localhost/path')

Test Client Methods

测试客户端方法

typescript
const client = testClient(app)

client.path.$get()
client.path.$post({ json: {} })
client.path[':id'].$get({ param: { id: '1' } })
client.path.$get({ query: { page: 1 } })
client.path.$post({ header: { 'X-Custom': 'v' } })
typescript
const client = testClient(app)

client.path.$get()
client.path.$post({ json: {} })
client.path[':id'].$get({ param: { id: '1' } })
client.path.$get({ query: { page: 1 } })
client.path.$post({ header: { 'X-Custom': 'v' } })

Related Skills

相关技能

  • hono-core - Framework fundamentals
  • hono-middleware - Middleware patterns
  • hono-validation - Request validation

Version: Hono 4.x Last Updated: January 2025 License: MIT
  • hono-core - 框架基础
  • hono-middleware - 中间件模式
  • hono-validation - 请求验证

版本: Hono 4.x 最后更新: 2025年1月 许可证: MIT