All tests use bun:test (Bun's built-in test runner -- not Jest, not Vitest).
just test # All tests via Turborepo
just test-backend # Backend tests only
just test-frontend # Frontend tests only
just test-e2e # Playwright E2E tests
just ci-check # Full CI pipeline (lint + format + type-check + build + test)Service logic, utilities, validation schemas. Located in packages/backend/tests/unit/.
- Mock external dependencies (DB, queues, auth) via
bun:testmock.module() - Test fixtures in
packages/backend/tests/fixtures.ts-- factory functions for all entity types - Pure function extraction: For DB-dependent logic, extract the core algorithm as a pure function and test directly (e.g., DAG validation, threshold decisions) -- avoids complex DB mocking
API endpoint validation. Located in packages/backend/tests/integration/.
- Test auth guards (401), validation (400), and response shapes (200) without a running DB
- Use
app.request()(Hono's built-in test helper) - Contract tests validate against OpenAPI spec
React component testing with Testing Library. Located in packages/frontend/tests/.
happy-domfor DOM simulation (not jsdom)fireEventfrom@testing-library/react(@testing-library/user-eventis not installed)- Always call
afterEach(cleanup)-- DOM persists in happy-dom
Test utilities in packages/frontend/tests/:
| File | Purpose |
|---|---|
test-utils.tsx |
renderWithProviders(), renderWithRouter(), cleanupAll() |
mocks/fetch.ts |
mockFetch() -- route-to-response mapping for global fetch |
mocks/websocket.ts |
installMockWebSocket() with simulateOpen/Close/Message |
fixtures/api-responses.ts |
Factory functions: mockLoginResponse, mockMeResponse, etc. |
utils/store-reset.ts |
resetAllStores() for Zustand cleanup |
Playwright for complete user workflows. Located in packages/frontend/e2e/.
Test the hot paths first:
- Hash submission ingestion
- Work unit distribution
- Agent heartbeat processing
- Campaign lifecycle transitions
- Authentication flows
Aim for 80%+ coverage. Agent API contract tests should validate responses against the OpenAPI spec to keep server and clients in sync.
mock.module()must come beforeimportof the module under test -- bun hoists itmock.modulemerges mock exports into the real module's ESM namespace. Non-mocked exports pass through. Mocked ones replace the real function for ALL test files in the same run.- Use
mockReset()notmockClear()inbeforeEach--mockClear()only resets call history, queuedmockResolvedValueOncevalues leak across tests - If tests pass in isolation but fail in the full suite, it is likely a module cache conflict. Separate conflicting mocks into different test files.
api.tsglobally intercepts all 401 responses as "Session expired" -- login tests must use 400 for invalid credentialsPermissionGuardhides elements -- tests asserting on guarded elements must seed the auth store with appropriate roles viauseAuthStore.setState()- Run tests per-package (
bun --filter @hashhive/frontend test), not from root -- rootbun testskips per-packagebunfig.toml(happy-dom), causingdocument is not defined
Auth tests mock the BetterAuth module rather than the old JWT service:
mock.module('../../src/lib/auth.js', () => ({
auth: {
api: {
getSession: async () => mockSession,
},
handler: async () => new Response('ok'),
},
}));Set mockSession to a user object for authenticated tests, or null for unauthenticated.