LobeHub Testing Guide
SkillDev toolsGuides your agent through writing, fixing, and improving Vitest tests including mocks and coverage.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the LobeHub Testing Guide skill
About this capability
Vitest testing guide. Use when writing or updating tests, fixing failing tests, improving coverage, debugging test issues, or setting up mocks.
What this skill tells your AI
The instructions your AI receives, as published by lobehub/lobehub in .agents/skills/testing/SKILL.md and read by ahel’s review.
Quick Reference
Commands:
# Run specific test file
bunx vitest run --silent='passed-only' '[file-path]'
# Database package (client-db, PGlite — default, skips BM25/pg_search)
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]'
Never run bun run test - it runs all 3000+ tests (~10 minutes).
Database models/repositories: every new file under
packages/database/src/models/**orsrc/repositories/**ships with a sibling__tests__/<name>.test.tsin the same PR. Use the real DB viagetTestDB()(integration style), guard BM25/full-text-search blocks withdescribe.skipIf(!isServerDB), and always test user-isolation. Seereferences/db-model-test.mdfor setup, schema gotchas, and the client-vs-server-db split.
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.spyOnovervi.mock- More targeted, easier to maintain - Tests must pass type check - Run
bun run type-checkafter writing tests - After 1-2 failed fix attempts, stop and ask for help
- Test behavior, not implementation details
- Regression tests for bug fixes - After fixing a bug, add a regression test that fails before the fix and passes after, to prevent recurrence. Skip pure style/CSS fixes (selector, hover, mask, spacing, color) when the only practical assertion would be source-string matching on the stylesheet — that is not a regression test worth shipping.
- No new component tests - Only update existing React component tests. Complex logic should be extracted into hooks and tested there instead
- All source changes before any test changes - Complete all source file edits first, then update tests in a separate pass. Interleaving disrupts reasoning about the source changes, especially across many files
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
UI Library Mocks (@lobehub/ui/base-ui)
Default: do NOT mock @lobehub/ui/base-ui — render the real components.
vitest.config.mts redirects the library's internal MotionProvider to a static
stub (tests/mocks/lobehubUiMotionProvider.tsx), so base-ui components render in
tests without the app-level ConfigProvider. Please wrap your app with <ConfigProvider> (or <MotionProvider>) in a test means that redirect is not in
effect (e.g. a package-local vitest config) — do not fix it by hand-mocking every
component.
When a test genuinely wants simplified DOM, compose the canonical stubs over the real module instead of writing a closed factory (closed factories break whenever the library migrates a component's import path):
vi.mock('@lobehub/ui/base-ui', async (importOriginal) => ({
...(await importOriginal<object>()),
...(await import('~base-ui-stubs')).baseUiStubs,
}));
~base-ui-stubs (tests/mocks/baseUiStubs.tsx) covers ActionIcon / Button /
Text / Tag / Avatar / Alert / toast / confirmModal / createModal with standard
aria semantics. A per-file factory is still fine when assertions need bespoke
testid conventions — but keep it composed over importOriginal so unknown
exports never go missing.
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-DDtoCurrent date: YYYY-MM-DD (TZ)→ update expected string
- Example: Tool data structure changed from
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.objectContainingonly 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
Signals
- GitHub stars
- 82k
- Forks
- 16k
- Last commit
- Sep 2026
- Hacker News mentions
- 20
ahel recommends instead
Advanced
- Catalog kind
- skill
- Gateway key
testing-lobehub- Source
- github.com/lobehub/lobehub