Skip to content

Mota — Testing Strategy

SDLC phase: Implementation (Chapter 5.2)
Purpose: What gets tested, how, and what "release-ready" means.

Governing principle: test effort follows trust. The interval engine and the sync queue get near-total coverage because a failure there destroys the product's only real asset — the user's belief that the record is true. The settings screen gets almost none because a failure there is annoying, not fatal.


1. Test pyramid

                    ┌─────────────┐
                    │   Manual    │   Exploratory + UAT
                    │   (human)   │   ~5% of effort
                    ├─────────────┤
                    │     E2E     │   8–10 critical journeys
                    │  (Playwright│   ~15% of effort
                    │   /Detox)   │
                    ├─────────────┤
                    │ Integration │   Sync, payments, RLS, notifications
                    │             │   ~30% of effort
                    ├─────────────┤
                    │    Unit     │   Interval engine, calculations, formatting
                    │             │   ~50% of effort
                    └─────────────┘

2. Unit testing

Coverage target: ≥70% overall; 100% on the interval engine.

Module What is tested Why it matters
Interval engine All 10 cases in 05-solution-design.md §4.5, plus property-based tests A wrong reminder is worse than no reminder
Fuel consumption km/l across irregular fill-ups, partial fills, missed odometer Wrong numbers destroy credibility instantly
Cost aggregation Totals, cost/km, period filtering, deleted-record exclusion Users will check the maths
Date/interval arithmetic Month-end edge cases (31 Jan + 1 month), leap years, timezone Classic source of silent bugs
Currency formatting USD/ZiG display, rounding, thousands separators A misplaced decimal on money is unforgivable
Sync conflict resolution Last-write-wins per field, monotonic odometer Data integrity

The interval engine is tested as a pure function. No mocking, no database, no time dependency — today is a parameter. This is why the design separates it from I/O.

Property-based tests worth writing:

  • km_remaining is never NaN and never computed from a negative interval.
  • For any record set, due_odo is always ≥ the most recent service odometer.
  • Classification is monotonic: if km_remaining increases, status can never escalate.

3. Integration testing

Area Test Pass condition
Offline sync Queue 50 writes offline, reconnect, force a mid-sync crash, restart All 50 apply exactly once; no duplicates; no losses
Idempotency Replay the same client_id 10 times One row created; 9 no-ops; all return success
RLS — user isolation Auth as A, query B's vehicle/records by ID Empty result or 403 — never data
RLS — garage isolation Auth as garage X, query garage Y's leads Empty result
PII gate Auth as garage, query lead without contact_released_at Phone number is null
Payment webhook Replay a Paynow webhook 5 times Invoice marked paid once; no duplicate credit
Payment webhook Forged signature Rejected 401, no state change
Reminder scheduler Seed due items, run the job twice in a day One notification per stage, not two
Reference data change Edit an interval, trigger recompute Affected vehicles' due dates update; others untouched

The RLS tests are a release blocker. A data-isolation defect is a CDPA incident, not a bug.


4. End-to-end testing

Eight journeys, run against a staging environment with a seeded database.

# Journey Critical assertion
E2E-1 New user: OTP → add vehicle → see dashboard Completes in <60s; dashboard shows correct interval
E2E-2 Log a service offline → reconnect Record appears; due date recalculates
E2E-3 Log 3 fill-ups km/l displays correctly from the third
E2E-4 Generate and share PDF PDF renders with all records, correct totals, QR present
E2E-5 Reminder → garage request → lead appears in console Lead visible in <60s with correct service and contact status
E2E-6 Garage marks lead booked → invoice includes the fee Fee appears once, at the right amount
E2E-7 Garage pays via Paynow sandbox Invoice marked paid; reconciliation matches
E2E-8 User deletes account PII purged within the flow; garage-side billing record anonymised

5. Device and environment matrix

Dimension Coverage Reason
Reference low-end device Tecno/Itel-class, 2GB RAM, Android 10 The NFR baseline. Every performance and usability test runs here first.
Modern mid-range 4GB RAM, Android 13 Common upgrade path
Oldest supported Android 8.0 (API 26) Contract floor
Network: 3G Throttled 400kbps, 300ms latency Real-world baseline for the target market
Network: offline Airplane mode Core requirement
Network: flaky 30% packet loss, intermittent Realistic rural/suburban reality
Font scaling 100% and 200% Accessibility
Throttled CPU 4× slowdown Simulates cheap silicon

Not covered in v1: iOS devices (out of scope), tablets (supported incidentally), desktop browsers for the consumer app.


6. User acceptance testing (UAT)

Run at the end of Gate 1 and again at Gate 3.

Participant group Count Tasks
First-time owners (never seen the app) 10 Add a vehicle; log a service; find what's due; export PDF
Existing users (2+ weeks of use) 10 Log 3 fill-ups; interpret a reminder; request a garage
Garage owners 5 Register a listing; respond to a lead; submit a price; find the invoice

UAT pass criteria:

Task Pass threshold
Add a vehicle unaided ≥9/10
Log a service in <30s ≥8/10
Correctly explain what "due in 900km" means ≥9/10
Export and share a PDF unaided ≥8/10
Garage responds to a lead unaided ≥5/5

Any task below threshold blocks release. UAT failure is a design failure, not a user failure.


7. Validation vs verification

CUT distinguishes these, and the distinction is genuinely useful. Both apply here.

Verification — "did we build it right?"

Check Method Owner
Requirements met Traceability from FR → test → pass Builder
NFRs met Reference-device measurements, bundle size, session data Builder
Code quality Review of every PR; no unreviewed merges Builder
Security RLS test suite, dependency audit, secret scanning Builder
Regression Full suite green before every release CI

Validation — "did we build the right thing?"

Question Measure Threshold When
Do users keep the record? 30-day retention ≥25% Gate 1
Do reminders get acted on? Acknowledge/open rate ≥40% Gate 2
Do garages get value? Lead → booked conversion ≥30% Gate 2
Do garages pay? Paying garages ≥5 Gate 3
Is the price index useful? Views per active user ≥1.5/mo Gate 3
Would users be upset if it disappeared? Exit survey: "very disappointed" ≥40% (Sean Ellis test) Gate 3

Verification can be fully automated. Validation cannot. Scheduling one is engineering; scheduling the other is the actual job.


8. Release criteria

A build ships to production only when all of the following are true:

# Criterion Blocking
R1 All unit and integration tests pass in CI Yes
R2 All 8 E2E journeys pass on the reference device Yes
R3 RLS isolation suite passes Yes — hard blocker
R4 No P1 or P2 defects open Yes
R5 Bundle size within budget (<200KB gzipped) Yes
R6 Reference-device cold start <3s Yes
R7 Crash-free sessions >99% in staging soak test Yes
R8 Privacy policy and deletion flow verified Yes before public launch
R9 Data controller registration confirmed Yes before public launch
R10 Rollback path tested Yes

9. What we deliberately don't test

Not tested Reason
iOS Out of scope
Payment processor internals Trust the provider; test our integration and webhook handling
ZINARA data accuracy No integration exists; user-entered data is the contract
OTP delivery across all networks continuously Tested at Gate 0 and spot-checked monthly; cannot be automated meaningfully
100% code coverage Diminishing returns; coverage follows trust, not a number
Load testing at scale At 10,000 users the system is nowhere near capacity; test at a plausible 10× and move on

10. Test data policy

  • Production data is never copied into staging.
  • Staging uses generated vehicles, records, and garages.
  • Phone numbers in tests use reserved ranges; no real contact attempts.
  • Payment tests use Paynow sandbox only.
  • Any screenshot or demo uses synthetic data — a real user's service history in a screenshot is a CDPA incident.