Skip to main content

Overview

Trackmart uses multiple testing frameworks to ensure code quality and functionality. The testing setup includes unit tests, widget tests, and integration tests.

Testing Dependencies

The app uses the following testing packages defined in pubspec.yaml:46-52:
pubspec.yaml
dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_driver:
    sdk: flutter
  test: any
  flutter_gherkin: ^0.0.12

flutter_test

For unit and widget tests

flutter_driver

For integration and E2E tests

flutter_gherkin

For BDD-style tests with Gherkin syntax

Test Structure

Directory Organization

sandtrack/
├── test/
│   └── widget_test.dart        # Widget tests
├── test_driver/
│   ├── app.dart                # Test app entry point
│   └── app_test.dart           # Integration tests
└── lib/
    └── main.dart               # Main app
test/ directory contains unit and widget tests that run in the Flutter test environment.test_driver/ directory contains integration tests that run against a real app instance.

Widget Tests

Basic Widget Test Structure

The widget test in test/widget_test.dart:1-31 demonstrates the testing pattern:
test/widget_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:sandtrack/main.dart';

void main() {
  testWidgets('Counter increments smoke test', (WidgetTester tester) async {
    // Build our app and trigger a frame.
    await tester.pumpWidget(MyApp());

    // Verify that our counter starts at 0.
    expect(find.text('0'), findsOneWidget);
    expect(find.text('1'), findsNothing);

    // Tap the '+' icon and trigger a frame.
    await tester.tap(find.byIcon(Icons.add));
    await tester.pump();

    // Verify that our counter has incremented.
    expect(find.text('0'), findsNothing);
    expect(find.text('1'), findsOneWidget);
  });
}

Running Widget Tests

1

Run All Tests

flutter test
2

Run Specific Test File

flutter test test/widget_test.dart
3

Run with Coverage

flutter test --coverage

Writing Widget Tests

// Find by text
find.text('Trackmart')

// Find by key
find.byKey(Key('request'))

// Find by widget type
find.byType(ElevatedButton)

// Find by icon
find.byIcon(Icons.add)

Integration Tests (Flutter Driver)

Test App Entry Point

The test_driver/app.dart:1-11 file sets up the app for driver testing:
test_driver/app.dart
import 'package:flutter_driver/driver_extension.dart';
import 'package:sandtrack/main.dart' as app;

void main() {
  // This line enables the extension
  enableFlutterDriverExtension();

  // Call the `main()` function of your app or call `runApp` with any widget you
  // are interested in testing.
  app.main();
}
This file enables the Flutter Driver extension, allowing the test runner to communicate with the app.

Integration Test Example

The integration test in test_driver/app_test.dart:1-36 demonstrates E2E testing:
test_driver/app_test.dart
import 'package:flutter_driver/flutter_driver.dart';
import 'package:test/test.dart';

void main() {
  group('Trackmart', () {
    FlutterDriver driver;

    setUpAll(() async {
      driver = await FlutterDriver.connect();
    });
    
    test('check flutter driver health', () async {
      Health health = await driver.checkHealth();
      print(health.status);
    });
    
    test('Flutter drive methods demo', () async {
      await driver.tap(find.byValueKey('request'));
      await driver.enterText('Hello !');
      await driver.waitFor(find.text('Hello !'));
      await driver.enterText('World');
      await driver.waitForAbsent(find.text('Hello !'));
      print('World');
      await driver.waitFor(find.byValueKey('request'));
      await driver.tap(find.byValueKey('request'));
      print('Button clicked');
      await driver.waitFor(find.byValueKey('rate'));
      await driver.scrollIntoView(find.byValueKey('rate'));
      await driver.waitFor(find.text('No requested deliveries'));
      print('I found you buddy !');
    });
    
    tearDownAll(() async {
      if (driver != null) {
        await driver.close();
      }
    });
  });
}

Running Integration Tests

1

Ensure Device is Running

Start an emulator or connect a physical device:
flutter devices
2

Run Driver Tests

flutter drive --target=test_driver/app.dart
Integration tests require a running device or emulator. They are slower than unit tests but provide end-to-end validation.

Using Keys for Testing

Trackmart uses Key widgets to make UI elements testable. Examples from home_page.dart:
// Button with key
MaterialButton(
  key: Key('request'),
  onPressed: () { /* ... */ },
  child: Text('Request'),
)

// Text with key
Text(
  '1 $_unit of $_product costs UGX ${(rate ?? 0).toStringAsFixed(0)}',
  key: Key('rate'),
)

Adding Keys to Widgets

1

Identify Testable Elements

Determine which widgets need to be tested (buttons, inputs, displays).
2

Add Key Parameter

ElevatedButton(
  key: Key('my_button'),
  onPressed: () {},
  child: Text('Click Me'),
)
3

Reference in Tests

// Widget test
await tester.tap(find.byKey(Key('my_button')));

// Driver test
await driver.tap(find.byValueKey('my_button'));

Flutter Gherkin (BDD Testing)

What is Gherkin?

Gherkin allows you to write tests in a human-readable format:
Feature: Order Delivery
  Scenario: User requests delivery
    Given I am on the request page
    When I enter quantity "5"
    And I tap the "Request" button
    Then I should see a confirmation dialog

Setting Up Gherkin Tests

1

Create Feature File

Create a .feature file in test_driver/features/:
mkdir -p test_driver/features
touch test_driver/features/order.feature
2

Write Feature Scenarios

test_driver/features/order.feature
Feature: Order Management
  Scenario: Request sand delivery
    Given I have the app open
    When I tap the "Request" tab
    And I enter quantity "5"
    And I select unit "Tonne"
    Then I should see the calculated price
3

Implement Step Definitions

Create step definitions that map to your Gherkin steps.
4

Run Gherkin Tests

flutter drive --target=test_driver/app.dart
Refer to the flutter_gherkin documentation for detailed setup and step definition examples.

Test Best Practices

Each test should be independent and not rely on the state from other tests.
setUp(() {
  // Initialize fresh state for each test
});

tearDown(() {
  // Clean up after each test
});
// Good
testWidgets('Request button is disabled when quantity is invalid', ...);

// Bad
testWidgets('Test 1', ...);
test('handles empty input', () { /* ... */ });
test('handles null values', () { /* ... */ });
test('handles maximum values', () { /* ... */ });
Mock Firebase and other external services to make tests fast and reliable:
import 'package:mockito/mockito.dart';

class MockDatabase extends Mock implements FirebaseDatabase {}

test('fetches user data', () async {
  final mockDb = MockDatabase();
  when(mockDb.reference()).thenReturn(/* mock reference */);
  // Test with mock
});

Testing Checklist

1

Unit Tests

Test individual functions and classes in isolation.
2

Widget Tests

Test UI components and interactions.
3

Integration Tests

Test complete user flows from start to finish.
4

Coverage Analysis

Aim for high code coverage:
flutter test --coverage
genhtml coverage/lcov.info -o coverage/html
open coverage/html/index.html

Common Testing Patterns

Testing Async Operations

testWidgets('displays data after loading', (WidgetTester tester) async {
  await tester.pumpWidget(MyApp());
  
  // Show loading state
  expect(find.byType(CircularProgressIndicator), findsOneWidget);
  
  // Wait for async operation
  await tester.pumpAndSettle();
  
  // Verify loaded state
  expect(find.text('Data Loaded'), findsOneWidget);
});

Testing Forms

testWidgets('submits form with valid data', (WidgetTester tester) async {
  await tester.pumpWidget(MyApp());
  
  // Enter text in fields
  await tester.enterText(find.byKey(Key('quantity')), '5');
  await tester.pump();
  
  // Submit form
  await tester.tap(find.byKey(Key('submit')));
  await tester.pumpAndSettle();
  
  // Verify result
  expect(find.text('Success'), findsOneWidget);
});

Testing Navigation

testWidgets('navigates to detail page', (WidgetTester tester) async {
  await tester.pumpWidget(MyApp());
  
  // Tap navigation element
  await tester.tap(find.text('View Details'));
  await tester.pumpAndSettle();
  
  // Verify new page is shown
  expect(find.text('Detail Page'), findsOneWidget);
});

Continuous Integration

Integrate tests into your CI/CD pipeline:
.github/workflows/test.yml
name: Test

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: subosito/flutter-action@v2
      - run: flutter pub get
      - run: flutter test
      - run: flutter drive --target=test_driver/app.dart
Test Early, Test Often: Run tests frequently during development to catch issues early.
Golden Tests: Consider using golden tests for visual regression testing of your UI components.

Build docs developers (and LLMs) love