Testing Guide
Testing Guide
Overview
HealthExporter uses XCTest unit tests that run automatically in GitHub CI on every push and pull request.
Test Structure
Tests live in HealthExporterTests/ (sibling to the main HealthExporter/ source folder):
| File | What it tests |
|---|---|
A1CSampleTests.swift |
A1CSample memberwise init, default source, value preservation |
CSVDocumentTests.swift |
CSVDocument content storage, UTF-8 round-trip, invalid encoding detection |
CSVGeneratorTests.swift |
CSV generation for weight, steps, glucose, A1C; unit conversion (kg→lbs); output formatting |
DateRangeOptionTests.swift |
DateRangeOption enum cases, raw values, displayName |
DayRangeSummaryFormatterTests.swift |
Date range summary text formatting |
ExportErrorTests.swift |
All ExportError.errorDescription branches with/without underlying errors |
ExportLogicTests.swift |
Export enablement, date range calculation, record limits, data availability, filename generation |
ExportPreviewEstimateTests.swift |
Row count rounding, byte estimation, confirmation threshold |
FHIRLabResultParserTests.swift |
FHIR JSON parsing — LOINC matching, missing fields, invalid data |
GlucoseSampleTests.swift |
GlucoseSampleMgDl init — values ≥20 accepted, values <20 rejected |
HealthKitQueryHelpersTests.swift |
Authorization type sets, day-aligned date ranges, A1C date filtering |
HealthMetricConfigTests.swift |
HealthMetrics static properties and LOINCCode constants |
SettingsEnumTests.swift |
Raw values, displayName, dateFormat, isUTC for all settings enums |
SettingsManagerTests.swift |
Default values, persisted value reads, invalid raw value fallbacks |
Running Tests Locally
In Xcode
- Open
HealthExporter.xcodeproj - Select Product → Test (⌘U)
- Results appear in the Test Navigator (⌘6)
From the command line
# Run tests against a specific simulator UUID to avoid duplicate-name collisions.
# Use `xcrun simctl list devices available` to find an ID on your machine.
xcodebuild test \
-project HealthExporter.xcodeproj \
-scheme HealthExporter \
-destination 'id=<SIMULATOR-UUID>' \
CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO \
| xcpretty
GitHub Actions CI
The workflow is defined in .github/workflows/ios-tests.yml and triggers on:
- Pushes to
mainoradd_tests - Pull requests targeting
main
Required setup — none for public repos
For a public repository, no secrets or environment variables are needed. The workflow uses CODE_SIGNING_ALLOWED=NO so no Apple Developer account is required to run simulator tests.
Optional setup for private repos or custom runners
| Setting | Purpose |
|---|---|
DEVELOPMENT_TEAM build override |
Only needed if you add entitlements-gated features to the test target |
Xcode version
The workflow automatically picks the newest installed Xcode on the runner:
XCODE=$(find /Applications -name "Xcode*.app" -maxdepth 1 -type d | sort -rV | head -1)
sudo xcode-select -s "$XCODE"
The project requires Xcode 26.x (for the iOS 26.0 deployment target). GitHub’s macos-15 runner should have this installed. If the runner only has an older Xcode, the build will fail with a deployment-target error — in that case, update runs-on in the workflow to a newer runner image (e.g. macos-15-xlarge or a future macos-26).
xcpretty
The workflow pipes xcodebuild output through xcpretty for readable CI logs. If xcpretty is not pre-installed on the runner, add:
- name: Install xcpretty
run: gem install xcpretty
before the “Run tests” step.
Adding New Tests
- Create a new
*Tests.swiftfile inHealthExporterTests/ - The
PBXFileSystemSynchronizedRootGroupin the Xcode project picks it up automatically — no.pbxprojedits needed for additional test files. - Use
@testable import HealthExporterto access internal types.
Manual Verification Matrix
The following items intentionally remain outside CI unit tests and require manual or device-based verification:
HealthKit Authorization
- Real HealthKit authorization dialogs (grant/deny/partial)
- Authorization state persistence across app launches
- Behavior when HealthKit is unavailable (e.g. iPad without HealthKit)
Clinical Health Records
- Real Clinical Health Records availability and entitlement
- Actual A1C record retrieval from health providers
- FHIR resource parsing with real clinical data (unit tests cover synthetic JSON)
File Export Integration
.fileExporter()integration with the iOS Files picker- Export to iCloud Drive, local storage, third-party file providers
- Correct filename display and file content in the saved CSV
Simulator vs Device Differences
- HealthKit data generated via
generateTestData()(simulator only) - HealthKit read authorization behavior (simulator grants silently; device shows dialogs)
- Clinical Records entitlement (device only — simulator cannot request clinical data)
SwiftUI View Behavior
- Splash screen animation timing (2-second delay)
- Settings sheet presentation and dismissal
- Navigation flow: Launch → DataSelection → Export
- Loading overlay during export preparation
Known Test Limitations
SettingsManagercannot be instantiated in tests due to a Combine/@Publishedmalloc crash when a second instance is created alongside the app’s@main@StateObject. Tests exercise the same logic throughUserDefaultsdirectly. A fix would require changing the test target to not use the app asTEST_HOST, or restructuringSettingsManagerto avoid the$property.dropFirst().sink()pattern.HealthKitManagerfetch methods are tightly coupled toHKHealthStoreand callback-based queries. The pure query construction logic is tested viaHealthKitQueryHelpers; actual data fetching requires a device with HealthKit data.