Write unit and integration tests for runtime services
epic-organization-feature-flags-runtime-task-007 — Write unit tests for the FeatureFlagInitializer (network success path, network failure fallback to cache, empty cache cold-start) and the FeatureFlagProvider (isEnabled returns correct value, rollout phase conditions applied, reactive update on flag toggle, organization switch triggers re-initialization). Use flutter_test and mock repository/cache dependencies.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 6 - 158 tasks
Can start after Tier 5 completes
Implementation Notes
Create test files mirroring the source structure: `test/feature_flags/feature_flag_initializer_test.dart` and `test/feature_flags/feature_flag_provider_test.dart`. Define shared test fixtures in a `test/feature_flags/fixtures.dart` file (sample flag maps, mock org IDs, rollout conditions). For Riverpod provider tests, prefer `ProviderContainer` over `WidgetTester` to keep tests lightweight. Use `addTearDown(container.dispose)` in each test to prevent provider leaks.
For the rollout phase condition tests, freeze time using a `FakeRolloutEvaluator` that accepts a configurable `DateTime` — do not rely on real `DateTime.now()`. For the org-switch re-initialization test, simulate by overriding `activeOrganizationProvider` mid-test and verifying the flag provider disposes and re-creates its stream subscription.
Testing Requirements
Pure unit tests using flutter_test. Use mocktail (preferred for Dart null-safety) or mockito for mocking FeatureFlagRepository, LocalFlagCache, and RolloutEvaluator dependencies. Structure tests in three groups: `group('FeatureFlagInitializer', ...)`, `group('FeatureFlagProvider.isEnabled', ...)`, `group('FeatureFlagProvider.reactive', ...)`. Each test should follow Arrange-Act-Assert pattern with descriptive test names.
Inject mocks via Riverpod `ProviderContainer` with `overrides` parameter for provider-level tests. For stream-based reactive tests, use `StreamController` to simulate repository events. Assert state changes using `container.read(provider)` after stream emission with `await Future.delayed(Duration.zero)` to flush microtasks.
The feature-flag-initializer must complete before any screen that checks feature flags renders. If the navigation shell is pushed concurrently with initialization (e.g., via a parallel Riverpod provider chain), some screens may query flags before they are loaded and incorrectly receive all-disabled defaults.
Mitigation & Contingency
Mitigation: Gate the main navigation shell render behind the feature-flag-initializer's Future by using a splash/loading screen that awaits the initialization provider. Use Riverpod's ref.watch on an initialization state enum (loading/ready/error) to block rendering.
Contingency: If race conditions are observed in testing, introduce an explicit initialization barrier using a ChangeNotifier or a dedicated `featureFlagsReadyProvider` that the router guard checks before allowing navigation.
If feature-flag-provider is watched by many widgets simultaneously and a flag map refresh triggers all of them to rebuild at once (e.g., after an organization switch), the app could experience a significant UI jank or dropped frames.
Mitigation & Contingency
Mitigation: Use select() on the provider to have each widget watch only the specific flag key it needs rather than the entire map. Ensure the provider uses equality checks so rebuilds only propagate when the specific flag value changes.
Contingency: If rebuild storms are measured via Flutter DevTools, refactor to a family provider keyed by flag key, so each widget subscribes only to its own flag's changes.