Skip to content

Cover by tests #37

@Kebechet

Description

@Kebechet

Testing Plan: Maui.Health Unit + Device Integration Tests

Context

Maui.Health has zero tests. We need to add both unit tests for pure logic (runs in CI without devices) and device integration tests that verify the actual Health Connect / HealthKit calls work on real Android emulators and iOS simulators.

Project conventions

  • Framework: xUnit (recommended for MAUI device test compatibility)
  • Assertions: Shouldly
  • Location: tests/ directory

Part 1: Unit Tests (no device needed)

Project

  • tests/Maui.Health.Tests/Maui.Health.Tests.csproj
  • Target: net9.0 (plain, no platform TFMs)
  • NuGet: xunit, xunit.runner.visualstudio, Shouldly, Microsoft.NET.Test.Sdk

What to test

Mapping logic (pure functions):

  • MetricDtoExtensions.GetHealthDataType<T>() - all DTO types map correctly
  • MetricDtoExtensions.GetRequiredPermission<T>() - returns correct permission type
  • GetAggregateMetricInfo() (Android) - maps HealthDataType to record class / metric field / unit
  • GetStatisticsInfo() (iOS) - maps HealthDataType to HKStatisticsOptions / HKUnit
  • GetUnitString() (iOS) - returns correct unit string per type
  • IsCumulativeType() - Steps and ActiveCalories are cumulative, others are not

Token serialization (iOS differential sync):

  • GetChangesToken produces valid JSON with Anchor + DataTypes
  • JSON token round-trips correctly (serialize then deserialize)
  • Anchor value is preserved through serialization

Workout duplicate detection:

  • FindDuplicates() - same activity type, different sources, overlapping times → detected
  • FindDuplicates() - same source → NOT detected (not a cross-source duplicate)
  • FindDuplicates() - different activity types → NOT detected
  • FindDuplicates() - times outside threshold → NOT detected
  • FindDuplicates() - activity type filter works

Model validation:

  • AggregatedResult has no Id/DataOrigin (structural assertion)
  • HealthChangesResult round-trips HasMore, NextToken, Changes list
  • HealthChange correctly holds Type, RecordId, DataType

Note: Some of the mapping functions are private static in platform files. We have two options:

  1. Extract them to internal static helpers and use [InternalsVisibleTo]
  2. Test them indirectly through the public API

Recommend option 1 with [InternalsVisibleTo("Maui.Health.Tests")] in the main project.


Part 2: Device Integration Tests

Project

  • tests/Maui.Health.DeviceTests/Maui.Health.DeviceTests.csproj
  • This is a full .NET MAUI app that deploys to device/emulator
  • Target frameworks: net10.0-android, net10.0-ios
  • Uses xUnit with xharness for headless execution
  • NuGet: xunit, Microsoft.DotNet.XHarness.TestRunners.Common, Microsoft.DotNet.XHarness.DefaultAndroidEntryPoint.Xunit
  • References Maui.Health project directly

Android setup requirements

  • Emulator with API 34+ (Health Connect built-in)
  • Tests auto-grant permissions via adb shell pm grant or test helper
  • Tests write data first, then verify reads/aggregates/deletes

iOS setup requirements

  • Simulator with iOS 17+
  • HealthKit available on simulator
  • Tests write data first via WriteHealthData, then verify reads
  • Note: iOS doesn't allow checking read permission status (by design), so we just verify data comes back

Test scenarios

Read/Write round-trip:

  1. Write a StepsDto → Read it back → verify Count matches
  2. Write an ActiveCaloriesBurnedDto → Read it back → verify Energy matches
  3. Write a WeightDto → Read it back → verify Value matches

Delete:

  1. Write a StepsDto → Get its ID from read → Delete by ID → Read again → verify gone

Aggregate:

  1. Write multiple StepsDto records → GetAggregatedHealthData → verify sum
  2. Write calories → GetAggregatedHealthData → verify sum

Aggregate by interval:

  1. Write steps spanning 3 days → GetAggregatedHealthDataByInterval with 1-day interval → verify 3 buckets

Differential sync:

  1. GetChangesToken → Write new data → GetChanges → verify upsert changes returned
  2. GetChanges again with new token → verify 0 changes (nothing new)

GetHealthRecord (read single):

  1. Write a record → Read all to get ID → GetHealthRecord by ID → verify matches

Permissions:

  1. Request read permission → verify IsSuccess

Running device tests

# Android (requires emulator running)
dotnet build tests/Maui.Health.DeviceTests -f net10.0-android
# Deploy and run via xharness
xharness android test --apk=path/to/app.apk --output-directory=out

# iOS (requires simulator, macOS only)
dotnet build tests/Maui.Health.DeviceTests -f net10.0-ios
# Deploy and run via xharness
xharness apple test --app=path/to/app.app --output-directory=out --target=ios-simulator-64

Files to create

Unit tests

  • tests/Maui.Health.Tests/Maui.Health.Tests.csproj
  • tests/Maui.Health.Tests/MetricDtoExtensionsTests.cs
  • tests/Maui.Health.Tests/DuplicateDetectionTests.cs
  • tests/Maui.Health.Tests/AggregateMetricMappingTests.cs
  • tests/Maui.Health.Tests/TokenSerializationTests.cs
  • tests/Maui.Health.Tests/ModelTests.cs

Device tests

  • tests/Maui.Health.DeviceTests/Maui.Health.DeviceTests.csproj
  • tests/Maui.Health.DeviceTests/MauiProgram.cs
  • tests/Maui.Health.DeviceTests/MainPage.xaml + .cs (test runner UI)
  • tests/Maui.Health.DeviceTests/Tests/ReadWriteTests.cs
  • tests/Maui.Health.DeviceTests/Tests/DeleteTests.cs
  • tests/Maui.Health.DeviceTests/Tests/AggregateTests.cs
  • tests/Maui.Health.DeviceTests/Tests/DifferentialSyncTests.cs

Modifications

  • src/Maui.Health/Maui.Health.csproj - Add [InternalsVisibleTo("Maui.Health.Tests")]
  • Solution file - Add both test projects

Verification

  • dotnet test tests/Maui.Health.Tests/ runs unit tests in CI
  • Device tests deploy and run on Android emulator / iOS simulator via xharness

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentation

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions