Testing Architecture
This document defines the comprehensive testing strategy, guidelines, and quality control measures for the BusFlow platform. Given our monorepo structure (pnpm workspaces) with Vue 3 and Tailwind CSS v4, our testing approach must be robust, scalable, and fully integrated into our CI/CD pipeline.
Testing Layers
We employ a modified testing pyramid strategy to balance execution speed with system reliability. Our goal is High Confidence, Low Friction.
| Test Type | Scope | Location | Tooling (Suggested) |
|---|---|---|---|
| Unit | Individual functions, components, or logic. | src/**/__tests__/*.test.ts (or colocated .spec.ts) | Vitest + Vue Test Utils |
| Integration | Interaction between modules (e.g., API -> DB). | tests/integration/**/*.test.ts | Vitest + MSW / Testcontainers |
| Application (E2E) | Full user flows in a browser environment. | tests/e2e/**/*.spec.ts | Playwright |
| Contract | Ensures API consumers/providers stay in sync. | tests/contract/ | Pact |
1. Unit Tests
Goal: Verify that individual functions, components, and composables work correctly in isolation.
- Focus: Pure functions, utility classes, and isolated UI components.
- Rule: No network calls or database access. Mock all external dependencies.
- Scope:
- Shared utility functions (e.g., in
packages/ui-domain). - Individual UI components (e.g., in
packages/ui-core). - Pinia stores and isolated composables (e.g., in
apps/*).
- Shared utility functions (e.g., in
- Tooling: Vitest (fast, native Vite integration, compatible with our Vue setup) + Vue Test Utils.
- Location: Colocated with the source code.
packages/ui-core/src/components/ui/button/Button.vue->packages/ui-core/src/components/ui/button/Button.spec.ts
2. Integration Tests
Goal: Verify that multiple units work together correctly. This includes component interactions, API service layers, and state management integration.
- Focus: Verifying that the service correctly interacts with the database, cache, or third-party APIs.
- Strategy: Use Testcontainers or a dedicated "Test DB" to run against real instances instead of mocks where possible on the backend. For frontend integration, mock external network requests using MSW.
- Scope:
- Complex features combining multiple UI components.
- Vue Router navigation flows within an app.
- Tooling: Vitest + Vue Test Utils + MSW (Mock Service Worker) / Testcontainers.
- Location: Dedicated
tests/integration/directory within each specific package or app.
3. Application (End-to-End / E2E) Tests
Goal: Verify the system as a whole from the user's perspective, running in a real browser against a production-like environment.
- Focus: Happy paths (e.g., Sign up -> Create Project -> Export) and critical user journeys.
- Environment: Run against a "Staging" or "Preview" environment that mirrors production.
- Tooling: Playwright (fast, reliable, cross-browser support).
- Location: A dedicated
tests/e2e/folder at the root of the app, or a centralizedpackages/e2e-testsworkspace if testing cross-app flows.
4. Contract Tests
Goal: Ensure that the API contracts between the frontend and backend remain strictly synchronized without needing full E2E environments.
- Tooling: Pact.
- Location: Dedicated
tests/contract/directory.
5. Additional Testing Layers
- Visual Regression Tests: Ensure UI components don't visually break unexpectedly. Integrated into Playwright or using a dedicated tool like Chromatic (Storybook integration).
- Static Analysis: TypeScript compilation, Biome (formatting + primary linting), and ESLint (Vue-specific + boundary enforcement). See Hybrid Linting Pipeline below.
Hybrid Linting Pipeline
We use a hybrid pipeline that combines Biome's speed with ESLint's Vue and architecture-specific capabilities. This replaces the traditional ESLint + Prettier setup.
Biome (Speed & Simplicity)
- Role: Formatter for the entire monorepo (TS, JS, JSON, CSS, Vue) and primary linter for standard JavaScript/TypeScript correctness, performance, and style rules.
- Replaces: Prettier entirely, plus the majority of ESLint rules.
- Impact: Handles ~95% of code analysis in milliseconds, drastically improving IDE feedback and pre-commit hook speed.
ESLint (Niche Architecture)
- Role: Highly stripped-down setup. Only runs
eslint-plugin-vue(Vue-specific template logic) and boundary enforcement plugins (e.g.,eslint-plugin-boundaries). - Excludes: We remove all formatting rules, standard TS rules, and
eslint-config-prettier. - Impact: ESLint does a fraction of its traditional work, eliminating the performance bottleneck while preserving strict architectural guardrails.
Writing Tests: Guidelines and Patterns
1. The AAA Pattern
Structure every test using the Arrange, Act, Assert pattern:
it('should increment counter', async () => {
// Arrange
const wrapper = mount(CounterComponent)
// Act
await wrapper.find('button').trigger('click')
// Assert
expect(wrapper.text()).toContain('Count: 1')
})2. Behavior-Driven Testing
Test what the code does (behavior/output), not how it does it (internal implementation).
- Do: Test that clicking "Submit" shows a success message.
- Don't: Test that clicking "Submit" sets
internalState.isSubmitting = true.
3. Mocking Strategy
- External APIs: Use MSW (Mock Service Worker) for frontend and network-level interceptors for backend. Avoid hardcoding mock data inside components; use Factories to generate fresh data for every test.
- Unit Tests: Mock complex dependencies (APIs, complex stores). Use
vi.mock(). - Integration Tests: Avoid mocking internal modules.
- E2E Tests: Run against a dedicated staging backend or a fully seeded local database. Avoid network mocking unless testing extremely difficult-to-replicate edge cases.
Quality Control of Tests
Tests itself must meet the standards of production code.
Tests are first-class citizens. Reject any PR without corresponding tests or with poorly written tests. Reviewers must check:
- Are edge cases covered?
- Are the assertions meaningful (no
expect(true).toBe(true))? - Is the AAA pattern followed?
2. Flakiness Management
Flaky tests destroy trust in the CI/CD pipeline.
- Zero Tolerance: If an E2E test is flaky, skip it (
test.skip()), ticket it, and fix it immediately. - Retries: Configure Playwright to retry failed tests automatically on CI (e.g.,
retries: 2). - Avoid Sleep: Never use hardcoded
setTimeoutorsleepin E2E tests. Use Playwright's built-in auto-waiting (await page.waitForSelector(...)).
3. Coverage Targets
While 100% coverage often proves counterproductive, we maintain baseline expectations:
- Business Logic & Shared Packages (
packages/*): High strictness. 80%+ coverage for core utilities, business logic, and UI components. - Apps (
apps/*): Focus on E2E critical paths and unit testing complex business logic (Stores, Composables).
4. Mutation Testing & Linting
- Mutation Testing: Occasionally run tools like Stryker to see if tests actually fail when code is intentionally broken. This proves the quality of our assertions.
- Linting for Tests: Use ESLint plugins (e.g.,
eslint-plugin-vitestoreslint-plugin-playwright) to enforce consistent naming and assertion styles.
Where Things Live (Example)
project-root/
├── docs/architecture/tests.md <-- This file
├── packages/
│ └── ui-core/
│ ├── src/
│ │ └── components/
│ │ └── ui/
│ │ └── button/
│ │ ├── Button.vue
│ │ └── Button.spec.ts <-- Unit tests (colocated)
├── apps/
│ └── workspace/
│ ├── tests/
│ │ ├── integration/ <-- Cross-module tests
│ │ ├── e2e/ <-- Playwright specs
│ │ └── fixtures/ <-- Reusable JSON/Data factories