Testing Guide - utourismboard/explore-uganda-application-documentation GitHub Wiki
Testing Guide
Table of Contents
Testing Overview
Testing Structure
test/
├── unit/
│   ├── repositories/
│   ├── services/
│   └── utils/
├── widget/
│   ├── screens/
│   └── components/
└── integration/
    ├── flows/
    └── e2e/
Test Dependencies
# pubspec.yaml
dev_dependencies:
  flutter_test:
    sdk: flutter
  integration_test:
    sdk: flutter
  mockito: ^5.4.4
  bloc_test: ^9.1.5
  golden_toolkit: ^0.15.0
Unit Testing
Writing Unit Tests
// Example unit test for TouristSite repository
void main() {
  late MockTouristSiteApi mockApi;
  late TouristSiteRepository repository;
  setUp(() {
    mockApi = MockTouristSiteApi();
    repository = TouristSiteRepository(api: mockApi);
  });
  group('TouristSiteRepository', () {
    test('getAllSites returns list of tourist sites', () async {
      // Arrange
      when(mockApi.getSites()).thenAnswer(
        (_) async => [
          {'id': '1', 'name': 'Site 1'},
          {'id': '2', 'name': 'Site 2'},
        ],
      );
      // Act
      final result = await repository.getAllSites();
      // Assert
      expect(result.length, 2);
      expect(result[0].id, '1');
      expect(result[0].name, 'Site 1');
      verify(mockApi.getSites()).called(1);
    });
    test('getSiteById throws exception when site not found', () {
      // Arrange
      when(mockApi.getSiteById('999')).thenThrow(NotFoundException());
      // Act & Assert
      expect(
        () => repository.getSiteById('999'),
        throwsA(isA<NotFoundException>()),
      );
    });
  });
}
Mocking
// Example mock using Mockito
@GenerateMocks([TouristSiteApi])
class MockTouristSiteApi extends Mock implements TouristSiteApi {}
// Example mock response
final mockSiteResponse = {
  'id': '1',
  'name': 'Murchison Falls',
  'description': 'Beautiful waterfall',
  'location': {
    'latitude': 2.2748,
    'longitude': 31.6799,
  },
  'images': ['image1.jpg', 'image2.jpg'],
};
Widget Testing
Widget Test Example
void main() {
  group('TouristSiteScreen', () {
    testWidgets('displays loading indicator when loading',
        (WidgetTester tester) async {
      // Arrange
      final mockProvider = MockTouristSiteProvider();
      when(mockProvider.isLoading).thenReturn(true);
      // Act
      await tester.pumpWidget(
        MaterialApp(
          home: ChangeNotifierProvider<TouristSiteProvider>.value(
            value: mockProvider,
            child: TouristSiteScreen(siteId: '1'),
          ),
        ),
      );
      // Assert
      expect(find.byType(CircularProgressIndicator), findsOneWidget);
    });
    testWidgets('displays error message on error',
        (WidgetTester tester) async {
      // Test implementation
    });
  });
}
Golden Tests
void main() {
  group('TouristSiteCard Golden Tests', () {
    testGoldens('matches golden file', (tester) async {
      // Arrange
      final builder = DeviceBuilder()
        ..addScenario(
          widget: TouristSiteCard(
            site: TouristSite(
              id: '1',
              name: 'Test Site',
              description: 'Test Description',
              images: ['test.jpg'],
            ),
          ),
        );
      // Act & Assert
      await tester.pumpDeviceBuilder(builder);
      await screenMatchesGolden(tester, 'tourist_site_card');
    });
  });
}
Integration Testing
Integration Test Example
void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
  group('end-to-end test', () {
    testWidgets('complete booking flow', (tester) async {
      // Launch app
      app.main();
      await tester.pumpAndSettle();
      // Navigate to tourist site
      await tester.tap(find.byKey(Key('site_card_1')));
      await tester.pumpAndSettle();
      // Fill booking details
      await tester.enterText(
        find.byKey(Key('date_picker')),
        '2024-04-01',
      );
      await tester.tap(find.byKey(Key('book_button')));
      await tester.pumpAndSettle();
      // Verify booking confirmation
      expect(find.text('Booking Confirmed'), findsOneWidget);
    });
  });
}
Test Coverage
Running Tests with Coverage
# Run all tests with coverage
flutter test --coverage
# Generate coverage report
genhtml coverage/lcov.info -o coverage/html
# Open coverage report
open coverage/html/index.html
Coverage Requirements
- Minimum coverage: 80%
- Critical paths: 90%
- UI components: 70%
- Utility functions: 95%
CI/CD Integration
GitHub Actions Example
name: Tests
on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.19.0'
      
      - name: Install dependencies
        run: flutter pub get
      
      - name: Run tests
        run: flutter test --coverage
      
      - name: Upload coverage
        uses: codecov/codecov-action@v2
        with:
          file: coverage/lcov.info
Test Automation
- 
Pre-commit Hooks # .git/hooks/pre-commit flutter test
- 
Continuous Testing - Run tests on every PR
- Block merging if tests fail
- Require code review on test changes
 
Best Practices
Testing Guidelines
- 
Test Organization - One test file per source file
- Clear test descriptions
- Proper test grouping
- Shared test utilities
 
- 
Test Quality - Test edge cases
- Handle async operations
- Clean up after tests
- Avoid test interdependence
 
- 
Performance - Mock heavy operations
- Use setUpandtearDown
- Group similar tests
- Optimize test execution
 
Common Patterns
// Example test pattern
void main() {
  late MockDependency mock;
  late SystemUnderTest sut;
  setUp(() {
    mock = MockDependency();
    sut = SystemUnderTest(mock);
  });
  tearDown(() {
    // Cleanup
  });
  group('feature test', () {
    test('happy path', () {
      // Arrange
      when(mock.someMethod()).thenReturn(value);
      // Act
      final result = sut.someMethod();
      // Assert
      expect(result, expectedValue);
      verify(mock.someMethod()).called(1);
    });
    test('error path', () {
      // Test implementation
    });
  });
}