关于 testing
面向 LobeHub 贡献者的测试指南,介绍 Web、软件包和桌面代码的测试选择、设计原则及项目约定。
主要功能
- 01运行指定 Vitest 测试
- 02数据库测试设置指南
- 03回归测试原则
- 04模拟与组件测试规则
- 05专项测试指南
适用场景
- 01开发时运行单个测试文件
- 02为数据库模型或仓库添加测试
- 03实现变更后评估失败测试
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ lobehub/ lobehub --skill testing安装后在 Claude Code 中输入/testing确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/agent-runtime-e2e.mddb-model-test.mddesktop-controller-test.mdelectron-ipc-test.mdzustand-store-action-test.md详细介绍
LobeHub 测试指南
快速参考
命令:
# Run specific test file
bunx vitest run --silent='passed-only' '[file-path]'
# Database package (client-db, PGlite — default, no pg_search; BM25 migrations skip themselves)
cd packages/database && bunx vitest run --silent='passed-only' '[file]'
# Database package (server-db, Postgres — BM25/pgvector parity, what CI measures coverage in)
cd packages/database && TEST_SERVER_DB=1 bunx vitest run --silent='passed-only' '[file]'
切勿运行 bun run test ——它会运行所有 3000 多个测试(约需 10 分钟)。
数据库模型/存储库:
packages/database/src/models/**或src/repositories/**下的每个新文件,都必须在同一个 PR 中附带同级的__tests__/<name>.test.ts。使用getTestDB()连接真实数据库(集成测试风格), 使用describe.skipIf(!isServerDB)跳过 BM25/全文搜索相关测试块,并始终测试用户隔离。 有关设置、架构注意事项以及客户端数据库与服务端数据库的区别,请参阅references/db-model-test.md。
测试类别
| 类别 | 位置 | 配置 |
|---|---|---|
| Web 应用 | src/**/*.test.ts(x) | vitest.config.ts |
| 软件包 | packages/*/**/*.test.ts | packages/*/vitest.config.ts |
| 桌面应用 | apps/desktop/**/*.test.ts | apps/desktop/vitest.config.ts |
核心原则
- 优先使用
vi.spyOn,而不是vi.mock——针对性更强,也更易维护。根目录的 Vitest 配置不会自动还原模拟;请在测试清理时使用vi.restoreAllMocks()还原 spy。 - 测试行为,而非实现细节
- 将原生
node:testCI 检查放在.github/scripts/下,避免其进入应用的 Vitest 测试发现路径。混用测试运行器时,推送前请确认预期的运行器,以及所属 Vitest 项目的文件发现范围。
- 将原生
- 为 Bug 修复编写回归测试 ——添加一个没有修复时会失败、有修复时会通过的回归测试;如果问题很容易复现,请先编写失败的测试。跳过纯样式/CSS 修复(选择器、悬停、遮罩、间距、颜色),如果唯一可行的断言方式是在样式表中匹配源代码字符串——这种回归测试不值得提交。
- 不要新增组件测试 ——只更新已有的 React 组件测试。复杂逻辑应提取到 hooks 中,并在那里进行测试。
UI 库模拟(@lobehub/ui/base-ui)
默认情况下,不要模拟 @lobehub/ui/base-ui——应渲染真实组件。
vitest.config.mts 会将该库内部的 MotionProvider 重定向到静态
桩(tests/mocks/lobehubUiMotionProvider.tsx),因此 base-ui 组件无需应用级 ConfigProvider
即可在测试中渲染。如果测试中出现 Please wrap your app with <ConfigProvider> (or <MotionProvider>),说明重定向未生效(例如使用了软件包本地的 vitest 配置)——不要通过手动模拟每个组件来解决。
如果测试确实需要简化的 DOM,请在真实模块上组合标准桩, 而不要编写封闭的工厂(封闭工厂会在库迁移组件的导入路径时失效):
vi.mock('@lobehub/ui/base-ui', async (importOriginal) => ({
...(await importOriginal<object>()),
...(await import('~base-ui-stubs')).baseUiStubs,
}));
~base-ui-stubs(tests/mocks/baseUiStubs.tsx)为 ActionIcon / Button /
Text / Tag / Avatar / Alert / toast / confirmModal / createModal 提供符合标准 aria 语义的桩。
如果断言需要自定义的 testid 约定,也可以使用单文件工厂——但请让它基于
importOriginal 进行组合,避免遗漏未知导出。
详细指南
请参阅 references/ 中的特定测试场景指南:
- 数据库模型测试:
references/db-model-test.md - Electron IPC 测试:
references/electron-ipc-test.md - Zustand Store Action 测试:
references/zustand-store-action-test.md - Agent Runtime E2E 测试:
references/agent-runtime-e2e.md - 桌面控制器测试:
references/desktop-controller-test.md
修复失败的测试——优化还是删除?
当测试因实现变更(而非 Bug)失败时,不要盲目修复;请先评估:
保留并修复(更新测试数据/断言)
- 行为测试:验证代码_做了什么_的测试(输出、副作用、用户可见行为)。只需更新模拟数据格式或预期值。
- 示例:工具数据结构从
{ name }变为{ function: { name } }→ 更新模拟数据 - 示例:输出格式从
Current date: YYYY-MM-DD变为Current date: YYYY-MM-DD (TZ)→ 更新预期字符串
- 示例:工具数据结构从
删除(过度指定、价值低)
- 参数转发测试:断言内部函数调用的确切参数的测试(例如,
expect(internalFn).toHaveBeenCalledWith(expect.objectContaining({ exact params })))——每次重构都会导致这类测试失败,而且它们与行为测试的覆盖内容重复。 - 与实现耦合的测试:验证代码内部_如何_工作,而不是它产出_什么_的测试。如果更高层级的测试已覆盖相同行为,低层级测试只会增加维护成本,不会提升覆盖率。
决策清单
- 测试是否验证了外部可观察到的行为(API 响应、数据库写入、渲染输出)?→ 保留
- 测试是否只验证了内部连线(哪个函数接收了哪些参数)?→ 检查行为测试是否已覆盖。如果是 → 删除
- 相同行为是否已在更高集成级别进行测试?→ 删除较低级别的重复测试
- 测试是否会在下一次常规重构中再次失败?→ 考虑将其提升到集成级别或删除
编写新测试时
- 优先使用集成级别断言(验证最终输出),而非白盒断言(验证内部调用)
- 仅在稳定的、面向公众的契约中使用
expect.objectContaining,不要用于会随重构而变化的内部参数形状 - 在边界处进行模拟(数据库、网络、外部服务),不要在内部模块之间进行模拟