import type { VextApp, VextConfig, VextServices } from "../types/app.js"; /** * createTestApp 选项 * * @see 10-testing.md §2.1 */ export interface CreateTestAppOptions { /** * 覆盖默认配置(深度合并到 test 默认配置之上) */ config?: Partial; /** * 是否加载 src/plugins/(默认 false — 测试环境默认不加载插件) */ plugins?: boolean; /** * 手动注册插件(替代自动扫描,精确控制测试依赖) */ setupPlugins?: (app: VextApp) => Promise | void; /** * 是否加载 `src/services/`(默认 true) * * 为 `true` 时,`service-loader` 扫描 `src/services/` 并自动加载所有 `.ts` 服务文件。 * `.ts` 文件通过 esbuild bundle 编译后加载,完整支持: * - TypeScript 语法及类型擦除 * - 服务文件内部的 `.js` 扩展名 import(TypeScript ESM 约定) * - 相对路径模块依赖(esbuild bundle 内联解析) * * **单元测试推荐**:设为 `false` + 配合 `mockServices`,避免加载真实服务: * * ```typescript * await createTestApp({ * services: false, * mockServices: { user: mockUserService }, * }) * ``` */ services?: boolean; /** * 手动注入 mock services(覆盖 service-loader 扫描结果) * * 如果同时 services=true,先加载真实 services,再用 mockServices 覆盖。 * 如果 services=false,仅使用 mockServices。 */ mockServices?: Partial; /** * 是否加载 src/routes/(默认 true — 集成测试需要路由) */ routes?: boolean; /** * 是否加载 src/middlewares/(默认 true) */ middlewares?: boolean; /** * 项目根目录(默认 process.cwd()) * * 用于定位 src/routes、src/services 等目录。 */ rootDir?: string; } /** * 测试 App 实例 * * @see 10-testing.md §2.1 */ export interface TestApp { /** 底层 VextApp 实例 */ app: VextApp; /** * 发送模拟 HTTP 请求(无需启动 HTTP 服务器) * 类似 supertest 的 API 风格 */ request: TestRequest; /** * 关闭 test app(触发 onClose 钩子、清理资源) * 务必在 afterEach / afterAll 中调用 */ close(): Promise; } /** * HTTP 请求模拟器 * * @see 10-testing.md §6 */ export interface TestRequest { get(path: string): TestRequestBuilder; post(path: string): TestRequestBuilder; put(path: string): TestRequestBuilder; patch(path: string): TestRequestBuilder; delete(path: string): TestRequestBuilder; options(path: string): TestRequestBuilder; head(path: string): TestRequestBuilder; } /** * 链式请求构造器 * * 支持链式调用设置请求参数,最终通过 await 或 .then() 执行请求。 * * @see 10-testing.md §6 */ export interface TestRequestBuilder extends PromiseLike { /** 设置请求头 */ set(key: string, value: string): this; /** 设置多个请求头 */ headers(headers: Record): this; /** 设置 query 参数 */ query(params: Record): this; /** 设置请求体(自动序列化为 JSON,设置 Content-Type) */ send(body: unknown): this; /** 设置 Content-Type */ type(contentType: string): this; } /** * 模拟 HTTP 响应 * * @see 10-testing.md §6 */ export interface TestResponse { /** HTTP 状态码 */ status: number; /** 响应头 */ headers: Record; /** 自动解析的 JSON 响应体 */ body: any; /** 原始响应文本 */ text: string; } /** * createTestApp — 测试用 App 工厂 * * 零配置创建测试用 app 实例,支持 mock services、路由级集成测试。 * 内部不启动 HTTP 服务器,通过 adapter.buildHandler() 模拟请求。 * * --- * * ### TypeScript 服务文件与 ESM 加载 * * 当 `services: true`(默认)时,`createTestApp()` 调用 `loadServices(app, 'src/services/')` * 加载 `.ts` 源文件。`service-loader` 内部会自动用 esbuild 将 `.ts` 文件 * bundle 编译为 `.mjs` 后加载,解决两个原生问题: * * 1. `ERR_UNKNOWN_FILE_EXTENSION` — Node.js 原生 ESM 不支持 `.ts` 扩展名 * 2. `.js → .ts` 重映射缺失 — TypeScript ESM 约定使用 `.js` 扩展名, * Node.js / Vite resolver 均不自动回退到 `.ts`; * esbuild bundle 阶段已完整处理此映射 * * **单元测试推荐**:若只测试路由逻辑,使用 `mockServices` 跳过真实服务加载, * 速度更快且完全隔离: * * ```typescript * const t = await createTestApp({ * services: false, // 不加载真实 .ts 服务文件 * mockServices: { * user: { findAll: vi.fn().mockResolvedValue([]) }, * }, * }) * ``` * * **集成测试**:保持 `services: true`(默认),`service-loader` 自动处理编译。 * * @param options 创建选项(全部可选) * @returns 包含 app、request、close 的测试 App 实例 * * @example * ```typescript * const t = await createTestApp({ * routes: true, * mockServices: { * user: { findAll: async () => ({ list: [], total: 0 }) }, * }, * }) * const res = await t.request.get('/users/list').query({ page: 1, limit: 10 }) * expect(res.status).toBe(200) * await t.close() * ``` * * @see 10-testing.md §2(接口规范) * @see 10-testing.md §11(内部实现概览) * @see IMPLEMENTATION-PLAN.md 任务 1.19 */ export declare function createTestApp(options?: CreateTestAppOptions): Promise;