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_idresolution, 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 coreServer (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 serverProto — 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 protoDashboard (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 dashboardPWA (apps/pwa) — Component Tests
Same approach as dashboard. Lighter surface since the PWA is read-heavy at MVP.
rangeday test pwaiOS (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 deviceWhat 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 platformsiOS tests require a macOS runner. Android unit tests run on any platform; instrumented tests require an emulator or device.