Busflow Docs

Internal documentation portal

Skip to content

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 TypeScopeLocationTooling (Suggested)
UnitIndividual functions, components, or logic.src/**/__tests__/*.test.ts (or colocated .spec.ts)Vitest + Vue Test Utils
IntegrationInteraction between modules (e.g., API -> DB).tests/integration/**/*.test.tsVitest + MSW / Testcontainers
Application (E2E)Full user flows in a browser environment.tests/e2e/**/*.spec.tsPlaywright
ContractEnsures 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/*).
  • 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 centralized packages/e2e-tests workspace 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:

typescript
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 setTimeout or sleep in 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-vitest or eslint-plugin-playwright) to enforce consistent naming and assertion styles.

Where Things Live (Example)

text
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

Internal documentation — Busflow