# hono/testing testClient 调用规范 ## 版本信息 | 版本 | 日期 | 描述 | 作者 | |------|------|------|------| | 1.0 | 2026-01-03 | 初始版本,基于故事010.003修复经验总结 | James (Claude Code) | ## 概述 本文档定义了使用 `hono/testing` 的 `testClient` 测试API路由时的正确调用方式,避免常见的调用错误。 ## 基本原理 `testClient` 会根据Hono路由的路径结构自动生成类型安全的客户端对象。关键是理解路径到属性的映射规则。 ### 核心映射规则 1. **路径分隔符 `/` 转换为嵌套对象** 2. **路径参数 `:param` 转换为 `[':param']` 索引** 3. **开头的 `/` 被移除** 4. **连续的 `/` 被压缩为单层嵌套** ## 调用方式详解 ### 1. 简单路由(无参数) ```typescript // 路由定义 const route = createRoute({ method: 'get', path: '/', // ✅ 根路径 // ... }); app.openapi(route, handler); // 测试调用 const client = testClient(app); const response = await client.$get({ // ✅ 使用 .$get() query: { page: 1 } }); ``` ```typescript // 路由定义 const route = createRoute({ method: 'post', path: '/users', // 单层路径 // ... }); app.openapi(route, handler); // 测试调用 const response = await client.users.$post({ // ✅ client.users.$post() json: userData }); ``` ### 2. 路由带参数 ```typescript // 路由定义 const route = createRoute({ method: 'get', path: '/:id', // 参数路径 // ... }); app.openapi(route, handler); // 测试调用 const response = await client[':id'].$get({ // ✅ client[':id'].$get() param: { id: 123 } }); ``` ```typescript // 路由定义 const route = createRoute({ method: 'put', path: '/users/:id', // 嵌套路由+参数 // ... }); app.openapi(route, handler); // 测试调用 const response = await client.users[':id'].$put({ // ✅ client.users[':id'].$put() param: { id: 123 }, json: updateData }); ``` ### 3. 嵌套路由 ```typescript // 路由定义 const route = createRoute({ method: 'get', path: '/admin/users/:id', // 多层嵌套+参数 // ... }); app.openapi(route, handler); // 测试调用 const response = await client.admin.users[':id'].$get({ // ✅ 嵌套调用 param: { id: 123 } }); ``` ## 常见错误 ❌ ### 错误1: 使用完整路径字符串 ```typescript // ❌ 错误:使用完整路径作为属性 const response = await client['/api/v1/users'].$get(); // ✅ 正确:去掉前缀,使用相对路径映射 const response = await client.users.$get(); ``` ### 错误2: 路径与模块定义不匹配 ```typescript // 模块路由定义为 /:id const route = createRoute({ path: '/:id', // ... }); // ❌ 错误:使用了完整路径 const response = await client['/api/v1/:id'].$get(); // ✅ 正确:使用模块内的相对路径 const response = await client[':id'].$get(); ``` ### 错误3: 忘记使用 `:id` 索引 ```typescript // 路由定义: path: '/users/:id' // ❌ 错误:直接访问id属性 const response = await client.users[id].$get(); // 编译错误 // ✅ 正确:使用字符串索引 const response = await client.users[':id'].$get({ param: { id } }); ``` ### 错误4: 嵌套路由使用错误的分隔方式 ```typescript // 路由定义: path: '/admin/users/:id' // ❌ 错误:用斜杠分隔 const response = await client['admin/users/:id'].$get(); // ❌ 错误:用点号分隔参数 const response = await client.admin.users.:id.$get(); // ✅ 正确:用嵌套对象+字符串索引 const response = await client.admin.users[':id'].$get(); ``` ## 后端模块开发规范关联 ### 路由定义规范 **模块内部路由应使用相对路径**(不以 `/` 开头的完整路径): ```typescript // ✅ 正确:模块内使用相对路径 const listRoute = createRoute({ method: 'get', path: '/', // 或 '/:id' middleware: [authMiddleware], // ... }); // ❌ 错误:模块内使用完整API路径 const listRoute = createRoute({ method: 'get', path: '/api/v1/admin/users', // 不应在模块内包含前缀 middleware: [authMiddleware], // ... }); ``` **Server包注册时添加完整前缀**: ```typescript // packages/server/src/index.ts import { unifiedAdvertisementAdminRoutes } from '@d8d/unified-advertisements-module'; // ✅ 正确:Server包注册时添加完整前缀 app.route('/api/v1/admin/unified-advertisements', unifiedAdvertisementAdminRoutes); ``` ### 测试调用规范 ```typescript // ✅ 正确:测试时使用模块的相对路径 const adminClient = testClient(unifiedAdvertisementAdminRoutes); // 路由定义: path: '/' const response = await adminClient.$get({ query: { page: 1 } }); // 路由定义: path: '/:id' const response = await adminClient[':id'].$put({ param: { id: 123 }, json: data }); ``` ## 完整示例对照表 | 路由定义 (模块内) | Server注册 | 测试调用方式 | |-----------------|-----------|-------------| | `path: '/'` | `.route('/api/v1/users', routes)` | `client.$get()` | | `path: '/users'` | `.route('/api/v1/users', routes)` | `client.users.$get()` | | `path: '/:id'` | `.route('/api/v1/users', routes)` | `client[':id'].$get()` | | `path: '/users/:id'` | `.route('/api/v1/users', routes)` | `client.users[':id'].$get()` | | `path: '/admin/users/:id'` | `.route('/api/v1/admin', routes)` | `client.admin.users[':id'].$get()` | ## 调用技巧 ### 1. 使用 TypeScript 类型提示 ```typescript const client = testClient(userRoutes); // IDE会自动补全可用的方法和路径 client.$get() // 如果路由有GET / client.users.$get() // 如果路由有GET /users client[':id'].$get() // 如果路由有GET /:id ``` ### 2. 查看路由定义确认调用方式 ```typescript // 查看路由文件中的 path 定义 // src/routes/users.ts const listRoute = createRoute({ method: 'get', path: '/', // ← 这里决定了测试调用方式 // ... }); // 根据path确定测试调用 await client.$get() // path: '/' → .$get() ``` ### 3. 调试错误信息 ```typescript // 如果看到类似错误: // "Cannot read property '$get' of undefined" // 说明路径映射不正确 // 检查: // 1. 路由 path 是否正确定义 // 2. testClient 是否使用了正确的路由实例 // 3. 是否有多余的路径前缀 ``` ## 总结 ### 快速参考 | 路由 path | testClient 调用 | |-----------|-----------------| | `/` | `.$get()`, `.$post()`, etc. | | `/:id` | `[':id'].$get()`, `[':id'].$put()`, etc. | | `/users` | `.users.$get()`, `.users.$post()`, etc. | | `/users/:id` | `.users[':id'].$get()`, `.users[':id'].$put()`, etc. | | `/admin/users/:id` | `.admin.users[':id'].$get()`, etc. | ### 核心原则 1. **模块内使用相对路径**:`path: '/'` 或 `path: '/:id'` 2. **Server注册时添加前缀**:`.route('/api/v1/xxx', routes)` 3. **测试时映射路径结构**:`/` → `.$method()`, `/:id` → `[':id'].$method()` 4. **嵌套路径反映为嵌套对象**:`/admin/users/:id` → `.admin.users[':id'].$method()` ## 相关文档 - [后端模块包开发规范](./backend-module-package-standards.md) - 路由定义规范 - [后端模块包测试规范](./backend-module-testing-standards.md) - 测试框架和集成测试 --- **文档状态**: 正式版 **创建原因**: 故事010.003修复过程中发现缺乏testClient调用规范 **适用范围**: 所有使用 hono/testing 的后端模块测试