LobeHub Testing Guide
Quick Reference
Commands:
# Run specific test file
bunx vitest run --silent='passed-only' '[file-path]'
# Database package (client)
cd packages/database && bunx vitest run --silent='passed-only' '[file]'
# Database package (server)
cd packages/database && TEST_SERVER_DB=1 bunx vitest run --silent='passed-only' '[file]'
Never run bun run test - it runs all 3000+ tests (~10 minutes).
Test Categories
| Category |
Location |
Config |
| Webapp |
src/**/*.test.ts(x) |
vitest.config.ts |
| Packages |
packages/*/**/*.test.ts |
packages/*/vitest.config.ts |
| Desktop |
apps/desktop/**/*.test.ts |
apps/desktop/vitest.config.ts |
Core Principles
- Prefer
vi.spyOn over vi.mock - More targeted, easier to maintain
- Tests must pass type check - Run
bun run type-check after writing tests
- After 1-2 failed fix attempts, stop and ask for help
- Test behavior, not implementation details
Basic Test Structure
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
beforeEach(() => {
vi.clearAllMocks();
});
afterEach(() => {
vi.restoreAllMocks();
});
describe('ModuleName', () => {
describe('functionName', () => {
it('should handle normal case', () => {
// Arrange → Act → Assert
});
});
});
Mock Patterns
// ✅ Spy on direct dependencies
vi.spyOn(messageService, 'createMessage').mockResolvedValue('id');
// ✅ Use vi.stubGlobal for browser APIs
vi.stubGlobal('Image', mockImage);
vi.spyOn(URL, 'createObjectURL').mockReturnValue('blob:mock');
// ❌ Avoid mocking entire modules globally
vi.mock('@/services/chat'); // Too broad
Detailed Guides
See references/ for specific testing scenarios:
- Database Model testing:
references/db-model-test.md
- Electron IPC testing:
references/electron-ipc-test.md
- Zustand Store Action testing:
references/zustand-store-action-test.md
- Agent Runtime E2E testing:
references/agent-runtime-e2e.md
- Desktop Controller testing:
references/desktop-controller-test.md
Fixing Failing Tests — Optimize or Delete?
When tests fail due to implementation changes (not bugs), evaluate before blindly fixing:
Keep & Fix (update test data/assertions)
- Behavior tests: Tests that verify what the code does (output, side effects, user-visible behavior). Just update mock data formats or expected values.
- Example: Tool data structure changed from
{ name } to { function: { name } } → update mock data
- Example: Output format changed from
Current date: YYYY-MM-DD to Current date: YYYY-MM-DD (TZ) → update expected string
Delete (over-specified, low value)
- Param-forwarding tests: Tests that assert exact internal function call arguments (e.g.,
expect(internalFn).toHaveBeenCalledWith(expect.objectContaining({ exact params }))) — these break on every refactor and duplicate what behavior tests already cover.
- Implementation-coupled tests: Tests that verify how the code works internally rather than what it produces. If a higher-level test already covers the same behavior, the low-level test adds maintenance cost without coverage gain.
Decision Checklist
- Does the test verify externally observable behavior (API response, DB write, rendered output)? → Keep
- Does the test only verify internal wiring (which function receives which params)? → Check if a behavior test already covers it. If yes → Delete
- Is the same behavior already tested at a higher integration level? → Delete the lower-level duplicate
- Would the test break again on the next routine refactor? → Consider raising to integration level or deleting
When Writing New Tests
- Prefer integration-level assertions (verify final output) over white-box assertions (verify internal calls)
- Use
expect.objectContaining only for stable, public-facing contracts — not for internal param shapes that change with refactors
- Mock at boundaries (DB, network, external services), not between internal modules
Common Issues
- Module pollution: Use
vi.resetModules() when tests fail mysteriously
- Mock not working: Check setup position and use
vi.clearAllMocks() in beforeEach
- Test data pollution: Clean database state in beforeEach/afterEach
- Async issues: Wrap state changes in
act() for React hooks
1---2name: testing3description: Testing guide using Vitest. Use when writing tests (.test.ts, .test.tsx), fixing failing tests, improving test coverage, or debugging test issues. Triggers on test creation, test debugging, mock setup, or test-related questions.4---56# LobeHub Testing Guide78## Quick Reference910**Commands:**1112```bash13# Run specific test file14bunx vitest run --silent='passed-only' '[file-path]'1516# Database package (client)17cd packages/database && bunx vitest run --silent='passed-only' '[file]'1819# Database package (server)20cd packages/database && TEST_SERVER_DB=1 bunx vitest run --silent='passed-only' '[file]'21```2223**Never run** `bun run test` - it runs all 3000+ tests (\~10 minutes).2425## Test Categories2627| Category | Location | Config |28| -------- | --------------------------- | ------------------------------- |29| Webapp | `src/**/*.test.ts(x)` | `vitest.config.ts` |30| Packages | `packages/*/**/*.test.ts` | `packages/*/vitest.config.ts` |31| Desktop | `apps/desktop/**/*.test.ts` | `apps/desktop/vitest.config.ts` |3233## Core Principles34351. **Prefer `vi.spyOn` over `vi.mock`** - More targeted, easier to maintain362. **Tests must pass type check** - Run `bun run type-check` after writing tests373. **After 1-2 failed fix attempts, stop and ask for help**384. **Test behavior, not implementation details**3940## Basic Test Structure4142```typescript43import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';4445beforeEach(() => {46 vi.clearAllMocks();47});4849afterEach(() => {50 vi.restoreAllMocks();51});5253describe('ModuleName', () => {54 describe('functionName', () => {55 it('should handle normal case', () => {56 // Arrange → Act → Assert57 });58 });59});60```6162## Mock Patterns6364```typescript65// ✅ Spy on direct dependencies66vi.spyOn(messageService, 'createMessage').mockResolvedValue('id');6768// ✅ Use vi.stubGlobal for browser APIs69vi.stubGlobal('Image', mockImage);70vi.spyOn(URL, 'createObjectURL').mockReturnValue('blob:mock');7172// ❌ Avoid mocking entire modules globally73vi.mock('@/services/chat'); // Too broad74```7576## Detailed Guides7778See `references/` for specific testing scenarios:7980- **Database Model testing**: `references/db-model-test.md`81- **Electron IPC testing**: `references/electron-ipc-test.md`82- **Zustand Store Action testing**: `references/zustand-store-action-test.md`83- **Agent Runtime E2E testing**: `references/agent-runtime-e2e.md`84- **Desktop Controller testing**: `references/desktop-controller-test.md`8586## Fixing Failing Tests — Optimize or Delete?8788When tests fail due to implementation changes (not bugs), evaluate before blindly fixing:8990### Keep & Fix (update test data/assertions)9192- **Behavior tests**: Tests that verify _what_ the code does (output, side effects, user-visible behavior). Just update mock data formats or expected values.93 - Example: Tool data structure changed from `{ name }` to `{ function: { name } }` → update mock data94 - Example: Output format changed from `Current date: YYYY-MM-DD` to `Current date: YYYY-MM-DD (TZ)` → update expected string9596### Delete (over-specified, low value)9798- **Param-forwarding tests**: Tests that assert exact internal function call arguments (e.g., `expect(internalFn).toHaveBeenCalledWith(expect.objectContaining({ exact params }))`) — these break on every refactor and duplicate what behavior tests already cover.99- **Implementation-coupled tests**: Tests that verify _how_ the code works internally rather than _what_ it produces. If a higher-level test already covers the same behavior, the low-level test adds maintenance cost without coverage gain.100101### Decision Checklist1021031. Does the test verify **externally observable behavior** (API response, DB write, rendered output)? → **Keep**1042. Does the test only verify **internal wiring** (which function receives which params)? → Check if a behavior test already covers it. If yes → **Delete**1053. Is the same behavior already tested at a **higher integration level**? → Delete the lower-level duplicate1064. Would the test break again on the **next routine refactor**? → Consider raising to integration level or deleting107108### When Writing New Tests109110- Prefer **integration-level assertions** (verify final output) over **white-box assertions** (verify internal calls)111- Use `expect.objectContaining` only for stable, public-facing contracts — not for internal param shapes that change with refactors112- Mock at boundaries (DB, network, external services), not between internal modules113114## Common Issues1151161. **Module pollution**: Use `vi.resetModules()` when tests fail mysteriously1172. **Mock not working**: Check setup position and use `vi.clearAllMocks()` in beforeEach1183. **Test data pollution**: Clean database state in beforeEach/afterEach1194. **Async issues**: Wrap state changes in `act()` for React hooks