Testing Strategy

Principle

Test business logic once in the core crate. Test integration points at each platform boundary. Don’t duplicate coverage — if the core crate validates a score correctly, the iOS and Android tests don’t re-test validation logic.

Testing Layers

Core Crate (libs/core) — Unit Tests

The most heavily tested layer. All client-safe business logic lives here and is pure Rust with no IO dependencies, making it fast and deterministic.

What to test:

  • Validation rules (field-level, format, business constraints)
  • Scoring calculations (points, penalties, pass/fail, edge cases)
  • Append-only score logic (void chains, replaces_score_id resolution, final score filtering)
  • Protobuf serialization/deserialization (round-trip encoding)
  • Offline queue management (enqueue, dequeue, dedup key collision)
  • Sync protocol logic (read-down parsing, write-up assembly)
  • Data model invariants

What NOT to test here:

  • Database queries (no DB in core)
  • Auth or billing (server-only)
  • UI rendering (platform-specific)
rangeday test core

Server (apps/server) — Integration Tests

Tests the server crate against a real Postgres instance. Covers auth flows, API handlers, database queries, and billing webhook handling.

What to test:

  • Auth flows (registration, login, JWT issuance, refresh, password reset, invite upgrade)
  • Authorization (role-based access — admin vs instructor vs member)
  • ConnectRPC handlers (request validation, response shape, error codes)
  • Database queries (correct filtering by org, tenant isolation)
  • Billing webhook processing (Stripe event → subscription state update)
  • Score deduplication at the database level
  • Placeholder user creation and merge

Test database: Each test run uses a fresh Postgres database (create, migrate, seed, test, drop). The CLI handles this.

rangeday test server

Proto — Schema Validation

Not logic tests — schema correctness checks run by Buf.

What to test:

  • Lint (naming conventions, field numbering)
  • Breaking changes (removed fields, changed types, reused field numbers)
rangeday test proto

Dashboard (apps/dashboard) — Component Tests

Dioxus component tests for the admin web app. Focus on interaction logic, not business logic (which is tested in core).

What to test:

  • Form behavior (drill builder, event builder, roster editor)
  • Navigation and routing
  • State management (data flows correctly between components)
  • Error display (ConnectRPC error codes rendered as user-facing messages)
rangeday test dashboard

PWA (apps/pwa) — Component Tests

Same approach as dashboard. Lighter surface since the PWA is read-heavy at MVP.

rangeday test pwa

iOS (apps/ios) — Swift Tests

Tests the Swift app layer — UI behavior and UniFFI binding integration.

What to test:

  • UniFFI binding integration (Swift calls Rust core, gets expected results)
  • SwiftUI view logic (state management, navigation)
  • Local persistence (SwiftData round-trips)
  • Offline queue behavior (scores queue locally, flush on connectivity)
  • Background sync scheduling (BGTaskScheduler registration)

What NOT to test:

  • Core business logic (tested in Rust)
  • Server behavior (tested in server integration tests)
rangeday test ios          # unit tests (macOS only)

Android (apps/android) — Kotlin Tests

Mirror of iOS testing strategy.

What to test:

  • UniFFI binding integration (Kotlin calls Rust core, gets expected results)
  • Jetpack Compose UI logic (state management, navigation)
  • Local persistence (Room round-trips)
  • Offline queue behavior
  • Background sync scheduling (WorkManager registration)
rangeday test android             # unit tests
rangeday test android --device    # instrumented tests on connected device

What We Don’t Test

  • Cross-platform E2E (e.g., iOS submits score → server stores → dashboard displays) — deferred until the product stabilizes. The shared core crate and generated ConnectRPC clients provide compile-time guarantees that reduce the need for runtime E2E tests early on.
  • Visual regression — not planned for MVP. Dioxus and native UI frameworks don’t have mature visual testing tooling yet.
  • Load/performance testing — deferred. Data volumes are small (scores are 100-200 bytes, events are under 50 KB). Revisit when targeting large-scale deployments.

Testing Boundaries

┌──────────────────────────────────────────────────┐
│                   Core Crate                     │
│         Unit tests: logic, validation,           │
│         scoring, serialization, sync             │
└──────────┬────────────┬────────────┬───────────┬─┘
           │            │            │           │
     ┌─────┴──────┐ ┌───┴─────┐ ┌────┴───┐ ┌─────┴────┐
     │  Server    │ │  Web    │ │  iOS   │ │ Android  │
     │            │ │         │ │        │ │          │
     │ Integration│ │Component│ │ Swift  │ │ Kotlin   │
     │ tests      │ │ tests.  │ │ tests  │ │ tests    │
     │ (Postgres) │ │         │ │(UniFFI)│ │ (UniFFI) │
     └─────┬──────┘ └─────────┘ └────────┘ └──────────┘
           │
     ┌─────┴──────┐
     │  Postgres  │
     │  (real DB) │
     └────────────┘

The core crate is the foundation — if its tests pass, every consumer inherits correct business logic. Platform tests focus only on their unique concerns: UI, persistence, bindings, and background scheduling.

CI

All tests run in CI on every PR:

rangeday test all          # runs everything for the current platform
rangeday check all         # type-check + lint across all platforms

iOS tests require a macOS runner. Android unit tests run on any platform; instrumented tests require an emulator or device.