Flutter and Dart Testing Best Practices

 

1. Unit Testing Basics

Setting Up Test Dependencies

dev_dependencies:
  flutter_test:
    sdk: flutter
  mockito: ^5.4.0
  build_runner: ^2.4.0

Basic Test Structure

void main() {
  group('User Service Tests', () {
    late UserService userService;
    late MockHttpClient mockClient;

    setUp(() {
      mockClient = MockHttpClient();
      userService = UserService(client: mockClient);
    });

    test('fetchUser returns user when successful', () async {
      // Arrange
      when(mockClient.get(any))
          .thenAnswer((_) async => Response('{"id": 1}', 200));

      // Act
      final user = await userService.fetchUser(1);

      // Assert
      expect(user.id, equals(1));
    });
  });
}
2. Widget Testing

Pump and Settle

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

  // Verify initial state
  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 the counter has incremented
  expect(find.text('1'), findsOneWidget);
  expect(find.text('0'), findsNothing);
});

Finding Widgets

// By key
find.byKey(Key('my-widget'))

// By type
find.byType(ElevatedButton)

// By text
find.text('Click me')

// By icon
find.byIcon(Icons.add)

// By semantic label
find.bySemanticsLabel('Add item')
3. Integration Testing

Setting Up Integration Tests

// integration_test/app_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:my_app/main.dart' as app;

void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();

  group('end-to-end test', () {
    testWidgets('tap on the floating action button',
        (WidgetTester tester) async {
      app.main();
      await tester.pumpAndSettle();

      // Verify the initial state
      expect(find.text('0'), findsOneWidget);

      // Act: Tap the increment button
      await tester.tap(find.byType(FloatingActionButton));
      await tester.pumpAndSettle();

      // Assert: Verify the counter is incremented
      expect(find.text('1'), findsOneWidget);
    });
  });
}
4. Mocking and Fakes

Creating a Mock with Mockito

// Generate mock class
@GenerateMocks([HttpClient])
import 'package:mockito/annotations.dart';

// Using the mock
class MockHttpClient extends Mock implements HttpClient {}

Creating a Fake

class FakeUserRepository implements UserRepository {
  final List _users = [];

  @override
  Future<User?> getUser(int id) async {
    return _users.firstWhere((user) => user.id == id);
  }

  @override
  Future saveUser(User user) async {
    _users.add(user);
  }
}
5. Test Coverage

Running Tests with Coverage

# Run tests with coverage
flutter test --coverage

# Generate HTML report (requires lcov)
genhtml coverage/lcov.info -o coverage/html

# Open coverage report
open coverage/html/index.html

Excluding Files from Coverage

// coverage:ignore-file
import 'package:flutter/material.dart';

// coverage:ignore-start
void main() {
  runApp(MyApp());
}
// coverage:ignore-end
6. Golden Tests

Creating a Golden Test

testWidgets('MyWidget matches golden file',
    (WidgetTester tester) async {
  await tester.pumpWidget(
    MaterialApp(
      home: MyWidget(),
    ),
  );

  await expectLater(
    find.byType(MyWidget),
    matchesGoldenFile('my_widget.png'),
  );
});

Platform-Specific Golden Tests

testWidgets('MyWidget matches golden file',
    (WidgetTester tester) async {
  await tester.pumpWidget(
    MaterialApp(
      home: MyWidget(),
    ),
  );

  await expectLater(
    find.byType(MyWidget),
    matchesGoldenFile('my_widget_${Platform.operatingSystem}.png'),
  );
});
7. Best Practices and Tips
  • Always follow the Arrange-Act-Assert pattern in tests
  • Use meaningful test descriptions that explain the behavior being tested
  • Keep tests focused and test one thing at a time
  • Use setup and teardown to avoid code duplication
  • Don't test implementation details, test behavior
  • Use test doubles (mocks, fakes, stubs) appropriately
  • Maintain test independence - tests should not depend on each other
  • Consider using test fixtures for complex data setup
  • Remember to test error cases and edge cases
  • Keep your tests maintainable and readable

This article was updated on January 19, 2025