Skip to main content

Writing Widget Tests

1. Writing Widget Tests

Example: Basic Widget Test

import 'package:flutter_test/flutter_test.dart';
import 'package:myapp/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);
  });
}
Method Description Usage
testWidgets Entry point for widget tests testWidgets('Test name', (WidgetTester tester) async { ... });
tester.pumpWidget Builds and mounts a widget tree await tester.pumpWidget(MyApp());
find Locates widgets in the widget tree find.text('Hello')
tester.tap Simulates a tap on a widget await tester.tap(find.byType(ElevatedButton));
tester.enterText Enters text into a TextField await tester.enterText(find.byType(TextField), 'Hello');
Note: Widget tests run in a simulated environment and do not require an actual device or emulator.

Example: Testing Button Tap

testWidgets('Button tap increments counter', (WidgetTester tester) async {
  await tester.pumpWidget(MyApp());

  // Tap the button
  await tester.tap(find.byType(ElevatedButton));
  await tester.pump();

  // Verify the counter updated
  expect(find.text('1'), findsOneWidget);
});

Example: Testing Text Input

testWidgets('TextField updates text', (WidgetTester tester) async {
  await tester.pumpWidget(MyApp());

  // Enter text into the field
  await tester.enterText(find.byType(TextField), 'Flutter');
  await tester.pump();

  // Verify the text is updated
  expect(find.text('Flutter'), findsOneWidget);
});

2. Using WidgetTester Class

The WidgetTester class is the core utility for writing widget tests in Flutter. It provides methods to build, interact with, and verify the state of widgets in a test environment.

Method Description Example
pump() Builds and renders the widget tree. Can be called with or without a duration to simulate time passage.
await tester.pump();
await tester.pump(const Duration(milliseconds: 500));
pumpWidget() Builds and renders a widget tree. Typically used once at the beginning of a test. await tester.pumpWidget(MyApp());
tap() Simulates a tap on a widget. Requires a Finder to locate the target widget. await tester.tap(find.text('Submit'));
enterText() Enters text into a TextField. Requires a Finder to locate the field. await tester.enterText(find.byType(TextField), 'Hello World');
text() Retrieves the text content of a widget. Useful for verifying that widgets display expected values. final text = tester.text(find.text('Hello World'));
widget() Retrieves a widget from the widget tree. Useful for inspecting properties of a specific widget. final widget = tester.widget(find.byType(Text));

Example: Basic Widget Test

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  testWidgets('Counter increments smoke test', (WidgetTester tester) async {
    // Build the widget
    await tester.pumpWidget(MaterialApp(home: Text('Hello World')));

    // Verify the text
    expect(find.text('Hello World'), findsOneWidget);
  });
}

3. Finding Widgets

Method Description Usage
find.byType() Finds widgets of a specific type final finder = find.byType(Text);
find.byKey() Finds widgets with a specific key final finder = find.byKey(Key('myKey'));
find.byIcon() Finds widgets with a specific icon final finder = find.byIcon(Icons.add);
find.byText() Finds widgets with specific text content final finder = find.byText('Hello World');
find.byTooltip() Finds widgets with a specific tooltip final finder = find.byTooltip('Save');
find.bySemanticsLabel() Finds widgets with a specific semantic label final finder = find.bySemanticsLabel('Submit');

Example: Using find.byType()

testWidgets('Find Text widget', (WidgetTester tester) async {
  await tester.pumpWidget(MyApp());

  final textFinder = find.byType(Text);
  expect(textFinder, findsOneWidget);
});

Example: Using find.byKey()

testWidgets('Find widget by key', (WidgetTester tester) async {
  await tester.pumpWidget(MyApp());

  final keyFinder = find.byKey(Key('saveButton'));
  expect(keyFinder, findsOneWidget);
});

Example: Using find.byText()

testWidgets('Find widget by text', (WidgetTester tester) async {
  await tester.pumpWidget(MyApp());

  final textFinder = find.byText('Welcome');
  expect(textFinder, findsOneWidget);
});

4. Testing User Interactions

Example: Tapping a Button

testWidgets('taps button and updates text', (WidgetTester tester) async {
  await tester.pumpWidget(MyApp());
  final button = find.text('Tap Me');
  expect(find.text('Hello'), findsNothing);
  await tester.tap(button);
  await tester.pumpAndSettle();
  expect(find.text('Hello'), findsOneWidget);
});
Method Description Notes
tester.tap() Taps a widget Triggers onTap callback
tester.doubleTap() Double taps a widget Triggers onDoubleTap callback
tester.longPress() Long presses a widget Triggers onLongPress callback
tester.drag() Drags a widget Triggers onDrag callbacks

Example: Dragging a Widget

testWidgets('drags slider', (WidgetTester tester) async {
  await tester.pumpWidget(MyApp());
  final slider = find.byType(Slider);
  await tester.drag(slider, const Offset(50, 0));
  await tester.pumpAndSettle();
  expect(find.text('Value: 50'), findsOneWidget);
});

Example: Typing in a TextField

testWidgets('types text into field', (WidgetTester tester) async {
  await tester.pumpWidget(MyApp());
  final textField = find.byType(TextField);
  await tester.enterText(textField, 'Hello World');
  await tester.pumpAndSettle();
  expect(find.text('Hello World'), findsOneWidget);
});
Note: Use tester.pumpAndSettle() after user interaction to ensure the UI updates before assertions.

Common Interaction Patterns

  • Tap: Use tester.tap() to simulate button presses
  • Drag: Use tester.drag() to simulate swipe or slider movements
  • Type: Use tester.enterText() to simulate text input
  • Double Tap: Use tester.doubleTap() for double-click scenarios
  • Long Press: Use tester.longPress() for context menu triggers

5. Verifying Widget Properties

Property Values Description
key Key Unique identifier for a widget in the widget tree
child Widget The child widget to be tested
text String The text content to verify in a widget
visible bool Whether the widget is expected to be visible on screen

Example: Verifying Widget Properties

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  testWidgets('Verify widget properties', (WidgetTester tester) async {
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: Text('Hello World'),
        ),
      ),
    );

    // Verify text content
    expect(find.text('Hello World'), findsOneWidget);

    // Verify visibility
    expect(find.text('Hello World'), findsOneWidget);
  });
}

6. Testing Widget Rebuilds

Testing widget rebuilds in Flutter ensures that your widgets update correctly when their state changes. This is crucial for maintaining UI consistency and responsiveness.

Method Description Usage
tester.pump() Triggers a rebuild of the widget tree await tester.pump();
tester.pumpWidget() Rebuilds the widget tree with a new widget await tester.pumpWidget(newWidget);
tester.pumpAndSettle() Continues pumping until no more rebuilds occur await tester.pumpAndSettle();

Example: Testing Rebuilds with State Changes

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  testWidgets('Rebuilds when counter changes', (WidgetTester tester) async {
    await tester.pumpWidget(
      MaterialApp(
        home: Scaffold(
          body: CounterWidget(),
        ),
      ),
    );

    // Initial state
    expect(tester.text(find.text('0')), findsOneWidget);

    // Simulate a state change
    await tester.tap(find.byType(ElevatedButton));
    await tester.pump();

    // Verify rebuild
    expect(tester.text(find.text('1')), findsOneWidget);
  });
}

class CounterWidget extends StatefulWidget {
  @override
  State<CounterWidget> createState() => _CounterWidgetState();
}

class _CounterWidgetState extends State<CounterWidget> {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text(count.toString()),
        ElevatedButton(
          onPressed: () {
            setState(() {
              count++;
            });
          },
          child: Text('Increment'),
        ),
      ],
    );
  }
}
Note: Use tester.pumpAndSettle() to ensure all animations and async operations complete before asserting the final state.
Warning: Avoid relying on tester.pump() without any arguments in tests, as it may not guarantee a full rebuild in all cases.

7. Using Finders and Matchers

Finder Description Usage Example
find.byType(WidgetType) Finds widgets of a specific type find.byType(Text)
find.byKey(Key) Finds widgets with a specific key find.byKey(const Key('myKey'))
find.byWidget(Widget) Finds a specific widget instance find.byWidget(MyWidget())
find.byText(String) Finds widgets with specific text content find.byText('Hello World')
find.byIcon(IconData) Finds widgets with a specific icon find.byIcon(Icons.add)
find.byTooltip(String) Finds widgets with a specific tooltip find.byTooltip('Save')

Example: Using Finders in a Test

import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/main.dart';

void main() {
  testWidgets('Verify text is displayed', (WidgetTester tester) async {
    await tester.pumpWidget(MyApp());

    // Find a Text widget with specific content
    expect(find.byText('Welcome'), findsOneWidget);
  });
}
Matcher Description Usage Example
findsOneWidget Matches exactly one widget expect(find.byType(Text), findsOneWidget)
findsNothing Matches no widgets expect(find.byType(NonExistentWidget), findsNothing)
findsNWidgets(int count) Matches exactly N widgets expect(find.byType(Text), findsNWidgets(3))
findsAtLeastOneWidget Matches one or more widgets expect(find.byType(Text), findsAtLeastOneWidget)
hasText(String text) Matches widgets containing specific text expect(find.byType(Text).first, hasText('Hello'))
isA<WidgetType>() Matches widgets of a specific type expect(find.byType(Text), isA<Text>())

Example: Using Matchers in a Test

import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/main.dart';

void main() {
  testWidgets('Verify widget count', (WidgetTester tester) async {
    await tester.pumpWidget(MyApp());

    // Check that there are exactly 3 Text widgets
    expect(find.byType(Text), findsNWidgets(3));
  });
}

Common Finder and Matcher Patterns

  • find.byType(WidgetType) is useful for verifying widget presence
  • find.byKey(Key) is essential for testing widgets with keys
  • find.byText(String) is the most common way to find UI elements by content
  • findsOneWidget ensures only one widget matches the criteria
  • findsNWidgets(int count) is useful for verifying collections or lists

8. Testing Async Widgets

Method Description Usage
pumpAndSettle() Waits for all async operations to complete await tester.pumpAndSettle();
pump() Triggers a widget rebuild await tester.pump(const Duration(milliseconds: 500));
pumpWidget() Initializes the widget under test await tester.pumpWidget(MyWidget());
Note: Use pumpAndSettle() to ensure all async operations (e.g., API calls) are completed before asserting results.

Example: Testing Async Data Fetch

testWidgets('Fetches and displays data', (WidgetTester tester) async {
  final mockData = {'name': 'Flutter'};
  when(mockApi.fetchData()).thenAnswer((_) async => mockData);

  await tester.pumpWidget(MyAsyncWidget(api: mockApi));
  await tester.pumpAndSettle();

  expect(find.text('Flutter'), findsOneWidget);
});

9. Using Golden Tests

Golden tests in Flutter are used to verify that the visual appearance of widgets matches a known "golden" image. They are particularly useful for ensuring UI consistency and catching unintended visual changes.

Property Description Example
goldenFile Path to the golden file used for comparison. test/goldens/my_widget.png
testFile Path to the test file that contains the golden test. test/my_widget_test.dart
widget The widget to be tested for visual appearance. MyWidget()

Example: Basic Golden Test Setup

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  testWidgets('MyWidget golden test', (WidgetTester tester) async {
    await tester.pumpWidget(
      MaterialApp(
        home: MyWidget(),
      ),
    );

    await expectLater(
      find.byType(MyWidget),
      matchesGoldenFile('golden/my_widget.png'),
    );
  });
}
Note: Golden tests require the golden file to be generated first using flutter test --update-goldens.
Warning: Golden tests can be brittle due to platform differences and rendering variations. Ensure tests are run in a consistent environment.

Golden Test Workflow

  • Create a new test file for the widget.
  • Use matchesGoldenFile to compare the widget’s output with a golden image.
  • Run flutter test --update-goldens to generate the initial golden file.
  • Re-run tests to ensure visual consistency.

10. Mocking Dependencies in Widget Tests

Dependency Mocking Approach Notes
HTTP Client MockHttpClient or Dio with MockAdapter Use http package or dio with mock adapters to simulate API responses
Database mockito or sqflite_mock Mock database queries and responses to isolate widget logic
Shared Preferences mock_shared_preferences Simulate key-value storage without actual persistence

Example: Mocking HTTP Client

// Using mockito for HTTP client mocking
import 'package:mockito/mockito.dart';
import 'package:http/http.dart' as http;

class MockHttpClient extends Mock implements http.Client {}

// In test
final mockClient = MockHttpClient();
when(mockClient.get(any)).thenAnswer(
  (_) async => http.Response('{"name": "Flutter"}', 200),
);

// Use mockClient in widget test
Note: Use mockito or mocktail for clean and maintainable mocks.
Warning: Avoid real network calls in widget tests to ensure fast and reliable test execution.

Was this article helpful?