# Flutter Testing Research Summary - 2025

## Research Overview

Comprehensive analysis of current Flutter testing best practices for 2025, specifically tailored to your BuyerKiosk Live Flutter app with:
- Flutter 3.38.3 / Dart 3.10.1
- Riverpod 3.x AsyncNotifier pattern
- Freezed 3.x data models
- GoRouter 17.x navigation
- Mocktail for mocking (already in pubspec)

---

## Key Findings & Recommendations

### 1. Testing Framework & Architecture

**Finding:** Flutter's three-tier testing pyramid (unit → widget → integration) remains the industry standard for 2025.

**Recommendation for BuyerKiosk:**
- **Unit Tests (50-60%)**: Provider logic, repositories, mappers, utility functions
- **Widget Tests (30-35%)**: Screens, dialogs, user interactions, navigation
- **Integration Tests (5-10%)**: Critical user journeys (auth, store navigation)

**Rationale:** Unit tests are fast (< 100ms) and cheap to maintain. Widget tests verify UI behavior. Integration tests validate real-world flows on actual devices.

**Implementation:**
```bash
# Run all tests
flutter test

# Run with coverage
flutter test --coverage

# Generate HTML report
genhtml coverage/lcov.info -o coverage/html && open coverage/html/index.html
```

### 2. Riverpod 3.x Testing (AsyncNotifier)

**Finding:** Riverpod 3.0 (Sept 2025) introduced significant testing improvements:
- `ProviderContainer.test()` eliminates boilerplate
- Listener-based verification is more intuitive than stream testing
- `Ref.mounted` property prevents async race conditions

**Key Patterns:**

#### Pattern A: Basic AsyncNotifier Test
```dart
test('loads data successfully', () async {
  // 1. Create container with mock override
  final container = ProviderContainer(
    overrides: [repositoryProvider.overrideWithValue(mockRepo)],
  );
  addTearDown(container.dispose); // IMPORTANT!

  // 2. Use listener to track state changes
  final listener = Listener<AsyncValue<List<Store>>>();
  container.listen(dashboardProvider, listener);

  // 3. Wait for async completion
  await Future<void>.value();
  await container.pump();

  // 4. Verify final state
  expect(container.read(dashboardProvider).value, equals(stores));
});
```

**Critical Point:** Fallback value registration is REQUIRED for Mocktail matchers:
```dart
setUpAll(() {
  registerFallbackValue(const AsyncLoading<List<Store>>());
  registerFallbackValue(const AsyncData<List<Store>>([]));
});
```

#### Pattern B: Testing Provider Refresh
```dart
await container.read(dashboardProvider.notifier).refresh();
verify(() => mockRepository.getAllStores()).called(2); // Called twice
```

#### Pattern C: Error State Testing
```dart
when(() => mockRepo.getData())
    .thenThrow(Exception('API Error'));

final container = ProviderContainer(overrides: [...]);
await Future<void>.value();
await container.pump();

expect(container.read(myProvider), isA<AsyncError>());
```

**Recommendation:** Always use listener pattern instead of direct state checks for AsyncValue, as `AsyncLoading != AsyncLoading` when carrying injected data.

### 3. Mocktail vs Mockito

**Comparison:**

| Aspect | Mocktail | Mockito |
|--------|----------|---------|
| Code Generation | Not required | Required (build_runner) |
| Setup Complexity | Minimal | More boilerplate |
| Null Safety | Built-in (Dart-first) | Via codegen |
| Maturity | Newer (growing community) | ~8 years stable |
| Learning Curve | Lower | Steeper |
| Maintenance | Lower | Build runner overhead |

**Finding:** Mocktail (already in your pubspec) is the modern standard for Dart/Flutter projects in 2025.

**Recommendation:** Continue using Mocktail. It's production-grade and preferred by Riverpod ecosystem.

**Key Mocktail Patterns:**
```dart
// Register fallback values
registerFallbackValue(const AsyncData<MyType>(null));

// Stub methods
when(() => mockRepo.getData())
    .thenAnswer((_) async => testData);

// Verify calls
verify(() => mockRepo.getData()).called(1);
verifyNever(() => mockRepo.getData());

// Use matchers
when(() => mockRepo.getById(any()))
    .thenAnswer((_) async => MyData());
```

### 4. Widget Testing with GoRouter

**Finding:** GoRouter 17.x requires proper setup for widget tests to work.

**Recommended Approach:**

#### Solution 1: Mock GoRouter (Recommended)
```dart
class MockGoRouter extends Mock implements GoRouter {}

final mockRouter = MockGoRouter();
when(() => mockRouter.push(any())).thenAnswer((_) async => null);

await tester.pumpWidget(
  TestApp(
    home: const MyScreen(),
    overrides: [goRouterProvider.overrideWithValue(mockRouter)],
  ),
);

// Verify navigation
verify(() => mockRouter.push('/store/bk01')).called(1);
```

#### Solution 2: Real Router (for navigation tests)
```dart
final router = createRouter(initialLocation: '/');

await tester.pumpWidget(
  MaterialApp.router(routerConfig: router),
);

router.go('/store/bk01');
await tester.pumpAndSettle();

expect(find.byType(StoreDetailScreen), findsOneWidget);
```

**Recommendation:** Use mock GoRouter for most widget tests. Use real router only for dedicated navigation testing.

### 5. Test Organization & Structure

**Finding:** Proper directory structure prevents test maintenance nightmares as codebase grows.

**Recommended Structure:**
```
test/
├── core/                           # Core layer tests
│   ├── network/
│   │   └── api_client_test.dart
│   └── services/
│       └── push_notification_service_test.dart
│
├── data/                           # Data layer tests
│   ├── models/
│   │   ├── store_model_test.dart
│   │   └── mappers/
│   │       └── store_mapper_test.dart
│   └── datasources/
│       └── store_remote_datasource_test.dart
│
├── domain/                         # Domain layer tests
│   └── repositories/
│       └── store_repository_test.dart
│
├── presentation/                   # Presentation layer tests
│   ├── providers/
│   │   └── dashboard_provider_test.dart
│   ├── screens/
│   │   └── dashboard_screen_test.dart
│   └── widgets/
│       └── error_widget_test.dart
│
└── fixtures/                       # Shared test data
    ├── test_mocks.dart
    └── test_data.dart
```

**Benefits:**
- Easy to locate related tests
- Clear separation of concerns
- Parallel test discovery by developers

### 6. Code Coverage & CI Integration

**Finding:** Automating coverage reporting with GitHub Actions catches regressions early.

**Recommended Setup:**

#### Local Coverage Generation
```bash
flutter test --coverage
genhtml coverage/lcov.info -o coverage/html
open coverage/html/index.html
```

#### GitHub Actions Integration
```yaml
- name: Run tests with coverage
  run: flutter test --coverage

- name: Upload to Codecov
  uses: codecov/codecov-action@v3
  with:
    file: ./coverage/lcov.info
    fail_ci_if_error: false
```

#### Coverage Thresholds
- **Target:** 70%+ for production code
- **Check:** Add CI job to enforce minimum coverage
- **Monitoring:** Use Codecov dashboard for trends

**Recommendation:** Start with 70% coverage threshold, increase to 80%+ over time as team adopts testing culture.

### 7. Testing Patterns for BuyerKiosk-Specific Features

#### A. Testing Paginated Notes Feed
```dart
test('loads notes with infinite scroll pagination', () async {
  when(() => mockRepo.getNotes(typeNum: 'bk01', page: 1))
      .thenAnswer((_) async => [note1, note2, ...]);

  when(() => mockRepo.getNotes(typeNum: 'bk01', page: 2))
      .thenAnswer((_) async => [note10, note11, ...]);

  final container = ProviderContainer(overrides: [...]);
  container.listen(workbookNotesProvider('bk01'), listener);

  // Scroll to bottom to trigger page 2
  await mockRepo.getNotes(typeNum: 'bk01', page: 2);

  verify(() => mockRepo.getNotes(typeNum: 'bk01', page: 2)).called(1);
});
```

#### B. Testing Permission-Based Access
```dart
test('only shows Edit Task List to managers and above', () async {
  // Test with Employee (level 4)
  final container = ProviderContainer(
    overrides: [accessLevelProvider.overrideWithValue(4)],
  );

  expect(
    container.read(canAccessPageProvider(AppPage.taskEdit)),
    isFalse,
  );

  // Test with Manager (level 2)
  final container2 = ProviderContainer(
    overrides: [accessLevelProvider.overrideWithValue(2)],
  );

  expect(
    container2.read(canAccessPageProvider(AppPage.taskEdit)),
    isTrue,
  );
});
```

#### C. Testing Task Completion Tracking
```dart
test('updates task status and displays who completed it', () async {
  final mockNotifier = MockTodayTasksNotifier();

  when(() => mockNotifier.updateTaskStatus(
    taskId: 1,
    status: 'completed',
    employeeId: 123,
  )).thenAnswer((_) async {});

  final container = ProviderContainer(overrides: [...]);

  // Simulate user completing task
  await container.read(todayTasksProvider.notifier)
      .updateTaskStatus(taskId: 1, status: 'completed');

  // Verify shows employee name and timestamp
  expect(find.text('John Doe'), findsOneWidget);
  expect(find.text(todayDate), findsOneWidget);
});
```

#### D. Testing Ably Real-Time Updates
```dart
test('updates UI when new note comment arrives via Ably', () async {
  final mockAbly = MockAblyRealtime();

  await tester.pumpWidget(TestApp(
    overrides: [ablyProvider.overrideWithValue(mockAbly)],
  ));

  // Simulate incoming message
  mockAbly.simulateMessage({
    'type': 'note:comment',
    'noteId': 1,
    'comment': 'New comment'
  });

  await tester.pumpAndSettle();

  // Verify UI updated
  expect(find.text('New comment'), findsOneWidget);
});
```

---

## Implementation Roadmap

### Phase 1: Foundation (Week 1)
- [ ] Create `test/fixtures/test_mocks.dart` with mock factories
- [ ] Create `test/helpers/test_app.dart` helper widget
- [ ] Add `.github/workflows/test.yml` CI pipeline
- [ ] Set up Codecov integration
- [ ] Document patterns in `TESTING_GUIDE.md`

### Phase 2: Provider Tests (Week 2-3)
- [ ] Unit test all providers (dashboard, store detail, queue, etc.)
- [ ] Test AsyncNotifier state transitions
- [ ] Test refresh/invalidation logic
- [ ] Test error handling and caching

### Phase 3: Screen Tests (Week 3-4)
- [ ] Widget tests for all main screens
- [ ] Test loading, error, and empty states
- [ ] Test user interactions (taps, scrolls, inputs)
- [ ] Test navigation with mocked GoRouter

### Phase 4: Integration Tests (Week 4-5)
- [ ] Auth flow: Install → Verify → Dashboard
- [ ] Critical journey: Dashboard → Store → Notes
- [ ] Task completion flow with permission checks

### Phase 5: Coverage & Optimization (Week 5+)
- [ ] Achieve 70%+ coverage
- [ ] Performance benchmarks for critical providers
- [ ] Continuous monitoring via CI/CD

---

## Best Practices Checklist

### Before Writing Tests
- [ ] Understand what you're testing (behavior, not implementation)
- [ ] Identify all possible states (loading, success, error, empty)
- [ ] Plan mock dependencies
- [ ] Review existing test fixtures

### While Writing Tests
- [ ] Use clear, descriptive test names
- [ ] Follow Arrange-Act-Assert structure
- [ ] Test one thing per test
- [ ] Use fixtures for reusable data
- [ ] Always dispose containers with `addTearDown()`
- [ ] Use matchers instead of equality for AsyncValue
- [ ] Await async operations before assertions

### Before Merging PR
- [ ] All tests pass (`flutter test`)
- [ ] Coverage maintained/improved (70%+)
- [ ] New code has corresponding tests
- [ ] Navigation tested with mocks
- [ ] Error paths tested
- [ ] No hardcoded delays or sleep()

---

## Common Pitfalls & Solutions

| Pitfall | Solution |
|---------|----------|
| "Container not disposed" | Always: `addTearDown(container.dispose)` |
| "Listener only called once" | Await: `await Future<void>.value(); await container.pump();` |
| "No GoRouter in context" | Override: `goRouterProvider.overrideWithValue(mockRouter)` |
| "AsyncLoading != AsyncLoading" | Use matchers: `any(that: isA<AsyncLoading>())` |
| "Widget not found after async" | Use: `await tester.pumpAndSettle()` not just `pump()` |
| "Tests pass locally, fail in CI" | Enable parallel: `--concurrency=1` for debugging |
| "Test data scattered everywhere" | Use fixtures: `test/fixtures/test_data.dart` |
| "Mock not being used" | Register fallback: `registerFallbackValue(...)` |

---

## Tools & Dependencies

### Already in Your `pubspec.yaml`
- `flutter_test` (SDK) - Core testing framework
- `flutter_riverpod` - Riverpod providers
- `mocktail: ^1.0.3` - Mocking library

### Should Add (Optional but Recommended)

```yaml
dev_dependencies:
  # Already have mocktail, no need to add mockito

  # Optional for performance testing
  # benchmark_harness: ^4.1.0

  # Optional for integration testing
  # integration_test:  # (included in Flutter SDK)
```

### Tools for CI/CD
- GitHub Actions (free) - Runs tests on push/PR
- Codecov (free for OSS) - Coverage reporting
- Flutter/Dart lints - Code quality

---

## Performance Benchmarks

Based on 2025 industry standards:

| Test Type | Expected Time | Your Target |
|-----------|---------------|-------------|
| Unit test | < 100ms | < 50ms per test |
| Widget test | 100-500ms | < 200ms per test |
| Integration test | 1-10s | < 5s per flow |
| Full test suite | < 2 minutes | < 90s |

**CI/CD Target:** All checks (test, lint, coverage) complete in < 5 minutes

---

## Resources Used in Research

### Official Documentation
- [Flutter Testing Overview](https://docs.flutter.dev/testing/overview) - Flutter 3.38.x
- [Riverpod Testing Guide](https://riverpod.dev/docs/essentials/testing) - Riverpod 3.0+
- [GoRouter Testing](https://guillaume.bernos.dev/testing-go-router/) - Navigation patterns

### Community Best Practices
- [Code with Andrea: AsyncNotifier Testing](https://codewithandrea.com/articles/unit-test-async-notifier-riverpod/)
- [Code with Andrea: Test Coverage](https://codewithandrea.com/articles/flutter-test-coverage/)
- [Mocktail Package](https://pub.dev/packages/mocktail)

### 2025 Industry Reports
- [Top Testing Practices for Flutter 2025](https://www.zartek.ca/top-testing-and-qa-practices-for-flutter-app-development/)
- [Flutter Unit Testing Guide 2025](https://www.bacancytechnology.com/blog/flutter-unit-testing)

---

## Summary: Key Takeaways

1. **Riverpod 3.x** uses `ProviderContainer.test()` with listener-based verification (major improvement over 2.x)
2. **Mocktail** is the modern choice over Mockito - simpler, no code generation, Dart-first
3. **Testing pyramid**: 50-60% unit, 30-35% widget, 5-10% integration
4. **GoRouter**: Mock for unit/widget tests, real router for navigation integration tests
5. **Coverage**: Target 70% for production code, use Codecov in CI/CD
6. **Fixtures**: Centralize test data to keep tests DRY and maintainable
7. **CI/CD**: GitHub Actions + Codecov provides free, powerful test automation
8. **Patterns**: Fallback values, listener verification, provider overrides are critical
9. **Performance**: Full test suite should run in < 2 minutes
10. **Maintenance**: Well-organized test structure pays dividends as codebase grows

---

## Files Provided

1. **TESTING_GUIDE.md** (12KB)
   - Comprehensive 10-section guide with code examples
   - Production-grade patterns for your app
   - Common issues and solutions

2. **test/fixtures/test_mocks.dart**
   - Reusable mock factories and extensions
   - Fallback value setup
   - Stubbing helpers for each repository

3. **test/presentation/providers/dashboard_provider_test_example.dart**
   - 6 example provider tests showing core patterns
   - Detailed comments explaining each step
   - Copy/modify for your other providers

4. **test/presentation/screens/dashboard_screen_test_example.dart**
   - 10 example widget tests
   - GoRouter mocking patterns
   - User interaction testing

5. **.github/workflows/test.yml**
   - Complete CI/CD pipeline
   - Coverage reporting with Codecov
   - Multiple job matrix (test, lint, coverage-check)

6. **TESTING_CHEAT_SHEET.md**
   - Quick command reference
   - Common patterns at a glance
   - Debugging tips

---

## Next Steps

1. Review `TESTING_GUIDE.md` - 20 min read
2. Copy `test/fixtures/test_mocks.dart` to your project
3. Run example tests: `flutter test test/presentation/providers/dashboard_provider_test_example.dart`
4. Add `.github/workflows/test.yml` for CI/CD
5. Write tests for your first provider (use example as template)
6. Set up Codecov integration
7. Gradually increase coverage to 70%+ over next 2 weeks

---

## Questions or Issues?

When implementing:
1. Check `TESTING_GUIDE.md` appendix for command reference
2. Review example test files for specific patterns
3. Use `TESTING_CHEAT_SHEET.md` for quick lookups
4. See "Common Issues & Solutions" section in this summary

---

**Last Updated:** December 5, 2025
**Flutter Version:** 3.38.3
**Riverpod Version:** 3.x
**Dart Version:** 3.10.1
