How to write and run tests in Flutter

Unit tests, widget tests, integration tests, and mocking in Flutter

, updated

Testing is critical for Flutter apps. Flutter provides three levels of testing: unit, widget, and integration.

Unit tests

Test a single function, method, or class in isolation:

# pubspec.yaml
dev_dependencies:
  flutter_test:
    sdk: flutter
// lib/calculator.dart
class Calculator {
  int add(int a, int b) => a + b;
  int divide(int a, int b) {
    if (b == 0) throw ArgumentError('Cannot divide by zero');
    return a ~/ b;
  }
}
// test/calculator_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/calculator.dart';

void main() {
  late Calculator calculator;

  setUp(() {
    calculator = Calculator();
  });

  test('add returns the sum of two numbers', () {
    expect(calculator.add(2, 3), 5);
  });

  test('divide throws on zero', () {
    expect(() => calculator.divide(10, 0), throwsArgumentError);
  });

  group('Calculator', () {
    test('add handles negative numbers', () {
      expect(calculator.add(-1, 1), 0);
    });

    test('divide returns integer result', () {
      expect(calculator.divide(10, 3), 3);
    });
  });
}

Run: flutter test test/calculator_test.dart

Widget tests

Test a single widget’s rendering and interaction:

// test/counter_widget_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/counter_widget.dart';

void main() {
  testWidgets('Counter increments when button pressed', (tester) async {
    // Render the widget
    await tester.pumpWidget(MaterialApp(home: CounterWidget()));

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

    // Tap the button
    await tester.tap(find.byType(FloatingActionButton));
    await tester.pump(); // rebuild after state change

    // Verify updated state
    expect(find.text('1'), findsOneWidget);
    expect(find.text('0'), findsNothing);
  });
}

Common widget test finders

find.text('Hello')              // by text
find.byType(Text)                // by widget type
find.byIcon(Icons.add)           // by icon
find.byKey(ValueKey('myKey'))    // by key
find.byWidget(myWidget)          // by widget instance
find.ancestor(
  of: find.text('Item'),
  matching: find.byType(ListTile),
)                               // by ancestor

Common widget test actions

await tester.tap(find.byIcon(Icons.add));     // tap
await tester.enterText(find.byType(TextField), 'hello'); // type text
await tester.scrollUntilVisible(find.text('Item 50'), 100); // scroll
await tester.drag(find.byType(Dismissible), Offset(-500, 0)); // swipe
await tester.longPress(find.text('Long press me')); // long press
await tester.pump();           // trigger a rebuild
await tester.pumpAndSettle(); // wait until no more frames

Mocking with Mocktail

dev_dependencies:
  mocktail: ^1.0.0
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';

// Create a mock
class MockUserRepository extends Mock implements UserRepository {}

void main() {
  late MockUserRepository mockRepo;

  setUp(() {
    mockRepo = MockUserRepository();
    // Register fallback values for any parameters
    registerFallbackValue(User(id: '', name: ''));
  });

  test('fetches users from repository', () async {
    // Arrange
    final users = [User(id: '1', name: 'Alice')];
    when(() => mockRepo.getUsers()).thenAnswer((_) async => users);

    // Act
    final result = await mockRepo.getUsers();

    // Assert
    expect(result, users);
    verify(() => mockRepo.getUsers()).called(1);
  });
}

Integration tests

Test the full app running on a device or emulator:

dev_dependencies:
  integration_test:
    sdk: flutter
// 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('full app flow', (tester) async {
      app.main();
      await tester.pumpAndSettle();

      // Verify home screen loads
      expect(find.text('Welcome'), findsOneWidget);

      // Navigate to details
      await tester.tap(find.text('Details'));
      await tester.pumpAndSettle();

      // Verify details screen
      expect(find.text('Detail Page'), findsOneWidget);
    });
  });
}

Run: flutter test integration_test/app_test.dart

Run all tests

# Run all unit and widget tests
flutter test

# Run with coverage
flutter test --coverage

# Run a specific test file
flutter test test/calculator_test.dart

# Run integration tests on a connected device
flutter test integration_test/app_test.dart