Spec-driven analytics + automated event correctness tests for iOS.
Most juniors ship UI. This repo showcases analytics data quality at scale — JSON-based event contracts, runtime validation, an in-memory recorder, and deterministic test infrastructure that catches broken analytics before they reach production.
Analytics data breaking silently affects business decisions, A/B test results, and growth metrics. By the time anyone notices, dashboards have been incorrect for weeks. This project solves that with a contract-first approach — every analytics event is validated against a JSON spec at compile-test time.
┌─────────────────────────────────────────────────────┐
│ SwiftUI App │
│ ┌──────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Product │→ │ Product │→ │ Checkout │ │
│ │ List │ │ Detail │ │ + Pay │ │
│ └────┬─────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ └───────────────┼─────────────────┘ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ EventLogger │ ← track(name, payload)
│ │ (Mini SDK) │ │
│ └────────┬────────────┘ │
│ ┌───────────┼───────────┐ │
│ ▼ ▼ ▼ │
│ ┌────────────┐ ┌────────┐ ┌──────────────┐ │
│ │ Validator │ │Recorder│ │AnalyticsClient│ │
│ │ (vs spec) │ │ (actor)│ │ (mock net) │ │
│ └─────┬──────┘ └───┬────┘ └──────────────┘ │
│ ▼ ▼ │
│ ┌──────────┐ ┌──────────────┐ │
│ │events.json│ │events_export │ │
│ │ (spec) │ │ .json │ │
│ └──────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────┘
Every event is defined in a contract:
{
"schema_version": "1.0",
"events": {
"product_viewed": {
"required": ["product_id", "price", "currency", "timestamp_ms"],
"optional": ["category"],
"types": { "product_id": "string", "price": "double", ... },
"allowed": { "currency": ["TRY", "USD", "EUR"] }
}
}
}The EventValidator checks every track() call against this spec: missing keys, wrong types, unknown fields, and disallowed values all produce clear errors.
- Xcode 15+ (Swift 5.9+)
- iOS 16.0+ simulator
open EventQualityLab.xcodeproj
# ⌘R to run on simulator# Unit tests
xcodebuild test \
-project EventQualityLab.xcodeproj \
-scheme EventQualityLab \
-destination 'platform=iOS Simulator,name=iPhone 16,OS=latest' \
-only-testing:EventQualityLabTests
# UI tests
xcodebuild test \
-project EventQualityLab.xcodeproj \
-scheme EventQualityLab \
-destination 'platform=iOS Simulator,name=iPhone 16,OS=latest' \
-only-testing:EventQualityLabUITestsEventQualityLab/
├── Core/
│ ├── Models/
│ │ ├── EventSpec.swift # Spec data models
│ │ ├── EventRecord.swift # Recorded event model
│ │ ├── CodableValue.swift # Type-erased Codable wrapper
│ │ └── Product.swift # Product data model
│ ├── EventSpecLoader.swift # Load & decode spec from bundle
│ ├── EventValidator.swift # 5-layer spec validation
│ ├── EventRecorder.swift # Thread-safe event store (actor)
│ ├── EventLogger.swift # Main SDK entry point
│ ├── EventLoggerConfiguration.swift # Config + launch arg detection
│ └── AnalyticsClient.swift # Protocol + mock network
├── Features/
│ ├── ProductList/ # List screen
│ ├── ProductDetail/ # Detail + add to cart
│ ├── Checkout/ # Mock payment
│ └── Debug/ # Export/reset panel
├── Resources/
│ ├── events.json # Event spec v1.0
│ ├── events_v2_breaking.json # Breaking change demo
│ └── products.json # Product catalog fixture
├── ContentView.swift # Navigation root
└── EventQualityLabApp.swift # App entry + config
EventQualityLabTests/
├── EventSpecLoadingTests.swift
├── EventValidatorTests.swift
├── EventLoggerTests.swift
├── EventRecorderTests.swift
└── BreakingChangeTests.swift # v2 spec breaking change demo
EventQualityLabUITests/
└── EventQualityLabUITests.swift # Full flow + event verification
| Checkout Flow | Event Export | Failing Test |
|---|---|---|
![]() |
![]() |
![]() |
Every track() call is validated against events.json at runtime. Missing fields, wrong types, and disallowed values are caught immediately — with assertionFailure in DEBUG.
UI tests launch with -ui_testing -reset_events_on_launch, navigate the full checkout flow, then export recorded events and verify the exact sequence. No flaky network calls.
The EventRecorder actor stores all events and exports them as JSON — to a file for debugging, to the clipboard for UI tests.
events_v2_breaking.json demonstrates what happens when a spec evolves: the BreakingChangeTests prove that old payloads fail under the new contract, catching regressions before they ship.
MockAnalyticsClient writes to a JSONL file instead of hitting a real endpoint. Tests are always deterministic.
GitHub Actions runs on every push and PR:
# .github/workflows/ci.yml
- Build project
- Run unit tests (EventQualityLabTests)
- Run UI tests (EventQualityLabUITests)The project is fully covered by automated tests to ensure data quality at every layer.
| Test Suite | Tests | Description |
|---|---|---|
EventValidatorTests |
13 | Validates types, required fields, and disallowed values |
EventSpecLoadingTests |
6 | Ensures JSON specs are correctly parsed from bundle |
EventRecorderTests |
6 | Verifies thread-safe event storage and JSON export |
EventLoggerTests |
5 | Tests the SDK pipeline (Validate -> Record) |
BreakingChangeTests |
3 | Proves that v2 spec changes correctly catch regressions |
- Verifies the full E-Commerce flow: Product List -> Detail -> Checkout -> Payment Success.
- Validates the exact sequence of 5 analytics events by inspecting the recorder export.
| Flag | Default | Description |
|---|---|---|
strictMode |
true |
Unknown keys cause validation failure |
debugAssertsEnabled |
true |
Invalid events crash in DEBUG |
isUITesting |
false |
Set via -ui_testing launch arg |
shouldResetOnLaunch |
false |
Set via -reset_events_on_launch |
useMockNetwork |
true |
Always mock in this demo |
Batuhan Küçükaydın
Software Engineer | Computer Engineer | iOS Developer
📫 LinkedIn • GitHub • Medium
MIT


