testing
lobehub/lobe-chat
Vitest testing guide for writing, fixing, and debugging tests in LobeHub projects.
What is testing?
A reference guide for testing in the LobeHub codebase using Vitest. Use this when writing or updating tests, fixing failing tests, improving coverage, debugging test issues, or setting up mocks across webapp, packages, and desktop app contexts.
- Run specific test files with targeted Vitest commands without running the full 3000+ test suite
- Set up and maintain database model and repository tests with integration-style patterns using getTestDB()
- Apply core testing principles: prefer vi.spyOn over vi.mock, test behavior not implementation, and add regression tests for bug fixes
- Mock dependencies effectively using spy patterns and vi.stubGlobal for browser APIs while avoiding broad module mocks
- Structure tests with Arrange-Act-Assert pattern and proper beforeEach/afterEach cleanup
- Evaluate and refactor failing tests by distinguishing behavior tests (keep and fix) from implementation-coupled tests (delete)
How to install testing
npx skills add https://github.com/lobehub/lobe-chat --skill testing- Bun package manager installed
- Vitest configured in the project (vitest.config.ts at root and in packages/*/apps/*)
- Access to LobeHub repository structure with packages/database, src/, and apps/desktop directories
How to use testing
- 1.Run a specific test file using `bunx vitest run --silent='passed-only' '[file-path]'` instead of `bun run test` to avoid running all 3000+ tests
- 2.For database package tests, choose client-db (default, PGlite) or server-db (Postgres with BM25) by setting TEST_SERVER_DB=1 environment variable
- 3.Structure new tests using the provided template with describe/it blocks, beforeEach/afterEach cleanup, and Arrange-Act-Assert pattern
- 4.Use vi.spyOn for mocking dependencies and vi.stubGlobal for browser APIs; avoid vi.mock for entire modules
- 5.After writing tests, run `bun run type-check` to ensure tests pass type checking
- 6.Review the detailed guides in references/ for database models, Electron IPC, Zustand stores, E2E testing, and desktop controllers
Use cases
- Writing new unit or integration tests for webapp components, package utilities, or desktop app features
- Fixing failing tests after implementation changes by updating mock data or assertions rather than blindly re-implementing
- Setting up database model tests with client-db (PGlite) and server-db (Postgres) variants, including BM25 and full-text-search coverage
- Debugging test failures caused by module pollution, mock setup issues, or async state problems
- Refactoring test suites to remove low-value param-forwarding tests and raise integration-level assertions
- Backend and full-stack developers writing tests for LobeHub packages and database models
- Frontend developers testing React components and Zustand store actions
- Desktop app developers testing Electron IPC and controller logic
- QA engineers and developers improving test coverage and fixing regressions
testing FAQ
Prefer vi.spyOn for targeted mocking of direct dependencies; it's easier to maintain and reason about. Use vi.stubGlobal for browser APIs like Image or URL. Avoid vi.mock for entire modules as it's too broad and causes maintenance issues.
Evaluate whether the test verifies externally observable behavior (keep and fix by updating mock data/assertions) or only internal wiring (delete if a higher-level test already covers it). Avoid keeping param-forwarding tests that break on every refactor.
Use getTestDB() for integration-style testing in sibling __tests__/<name>.test.ts files. Guard BM25/full-text-search blocks with describe.skipIf(!isServerDB). Always test user-isolation and refer to references/db-model-test.md for schema gotchas.
It runs all 3000+ tests which takes ~10 minutes. Instead, run specific test files with `bunx vitest run --silent='passed-only' '[file-path]'` to get faster feedback during development.
Don't write new component tests. Extract complex logic into hooks and test those instead. Only update existing React component tests when necessary. This keeps tests focused on testable logic rather than implementation details.
Full instructions (SKILL.md)
Source of truth, from lobehub/lobe-chat.
name: testing description: 'Vitest testing guide. Use when writing or updating tests, fixing failing tests, improving coverage, debugging test issues, or setting up mocks.' user-invocable: false
LobeHub Testing Guide
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
- 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
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
Related skills
More from lobehub/lobe-chat and the wider catalog.

typescript
LobeHub TypeScript style and type-safety guide for consistent, type-safe code.

zustand
LobeHub Zustand state management conventions for store slices, actions, and optimistic updates.

drizzle
Drizzle ORM schema and query patterns for PostgreSQL databases in LobeChat.

hotkey
Add or edit LobeHub keyboard shortcuts with proper scoping, conflict detection, and i18n support.

add-provider-doc
Add documentation for a new AI provider with usage guides, environment variables, Docker config, and image resources.

add-setting-env
Add server-side environment variables to control default values for user settings.