- Writing Flutter Widget Tests
- Contents
- Setup & Configuration
- Core Components
- Workflow: Implementing a Widget Test
- Interaction & State Management
- Examples
- Setup & Configuration
- Ensure the testing environment is properly configured before authoring widget tests.
- Add the
- flutter_test
- dependency to the
- dev_dependencies
- section of
- pubspec.yaml
- .
- Place all test files in the
- test/
- directory at the root of the project.
- Suffix all test file names with
- _test.dart
- (e.g.,
- widget_test.dart
- ).
- Core Components
- Utilize the following
- flutter_test
- components to interact with and validate the widget tree:
- WidgetTester
-
- The primary interface for building and interacting with widgets in the test environment. Provided automatically by the
- testWidgets()
- function.
- Finder
-
- Locates widgets in the test environment (e.g.,
- find.text('Submit')
- ,
- find.byType(TextField)
- ,
- find.byKey(Key('submit_btn'))
- ).
- Matcher
- Verifies the presence or state of widgets located by a
Finder
(e.g.,
findsOneWidget
,
findsNothing
,
findsNWidgets(2)
,
matchesGoldenFile
).
Workflow: Implementing a Widget Test
Copy the following checklist to track progress when implementing a new widget test.
Task Progress
Step 1: Define the test.
Use
testWidgets('description', (WidgetTester tester) async { ... })
.
Step 2: Build the widget.
Call
await tester.pumpWidget(MyWidget())
to render the UI. Wrap the widget in a
MaterialApp
or
Directionality
widget if it requires inherited directional or theme data.
Step 3: Locate elements.
Instantiate
Finder
objects for the target widgets.
Step 4: Verify initial state.
Use
expect(finder, matcher)
to validate the initial render.
Step 5: Simulate interactions.
Execute gestures or inputs (e.g.,
await tester.tap(buttonFinder)
).
Step 6: Rebuild the tree.
Call
await tester.pump()
or
await tester.pumpAndSettle()
to process state changes.
Step 7: Verify updated state.
Use
expect()
to validate the UI after the interaction.
Step 8: Run and validate.
Execute
flutter test test/your_test_file_test.dart
.
Step 9: Feedback Loop.
Review test output -> identify failing matchers -> adjust widget logic or test assertions -> re-run until passing.
Interaction & State Management
Apply the following conditional logic based on the type of interaction or state change being tested:
If testing static rendering:
Call
await tester.pumpWidget()
once, then immediately run
expect()
assertions.
If testing standard state changes (e.g., button taps):
Call
await tester.tap(finder)
.
Call
await tester.pump()
to trigger a single frame rebuild.
If testing animations, transitions, or asynchronous UI updates:
Trigger the action (e.g.,
await tester.drag(finder, Offset(500, 0))
).
Call
await tester.pumpAndSettle()
to repeatedly pump frames until no more frames are scheduled (animation completes).
If testing text input:
Call
await tester.enterText(textFieldFinder, 'Input string')
.
If testing items in a dynamic or long list:
Call
await tester.scrollUntilVisible(itemFinder, 500.0, scrollable: listFinder)
to ensure the target widget is rendered before interacting with it.
Examples
High-Fidelity Widget Test Implementation
Target Widget (
lib/todo_list.dart
):
import
'package:flutter/material.dart'
;
class
TodoList
extends
StatefulWidget
{
const
TodoList
(
{
super
.
key
}
)
;
@override
State
<
TodoList
createState ( ) =
_TodoListState ( ) ; } class _TodoListState extends State < TodoList
{ final todos = < String
[ ] ; final controller = TextEditingController ( ) ; @override Widget build ( BuildContext context ) { return MaterialApp ( home : Scaffold ( body : Column ( children : [ TextField ( controller : controller ) , Expanded ( child : ListView . builder ( itemCount : todos . length , itemBuilder : ( context , index ) { final todo = todos [ index ] ; return Dismissible ( key : Key ( ' $ todo $ index ' ) , onDismissed : ( _ ) =
setState ( ( ) =
todos . removeAt ( index ) ) , child : ListTile ( title : Text ( todo ) ) , ) ; } , ) , ) , ] , ) , floatingActionButton : FloatingActionButton ( onPressed : ( ) { setState ( ( ) { todos . add ( controller . text ) ; controller . clear ( ) ; } ) ; } , child : const Icon ( Icons . add ) , ) , ) , ) ; } } Test Implementation ( test/todo_list_test.dart ): import 'package:flutter/material.dart' ; import 'package:flutter_test/flutter_test.dart' ; import 'package:my_app/todo_list.dart' ; void main ( ) { testWidgets ( 'Add and remove a todo item' , ( WidgetTester tester ) async { // 1. Build the widget await tester . pumpWidget ( const TodoList ( ) ) ; // 2. Verify initial state expect ( find . byType ( ListTile ) , findsNothing ) ; // 3. Enter text into the TextField await tester . enterText ( find . byType ( TextField ) , 'Buy groceries' ) ; // 4. Tap the add button await tester . tap ( find . byType ( FloatingActionButton ) ) ; // 5. Rebuild the widget to reflect the new state await tester . pump ( ) ; // 6. Verify the item was added expect ( find . text ( 'Buy groceries' ) , findsOneWidget ) ; // 7. Swipe the item to dismiss it await tester . drag ( find . byType ( Dismissible ) , const Offset ( 500 , 0 ) ) ; // 8. Build the widget until the dismiss animation ends await tester . pumpAndSettle ( ) ; // 9. Verify the item was removed expect ( find . text ( 'Buy groceries' ) , findsNothing ) ; } ) ; }
flutter-add-widget-test
安装
npx skills add https://github.com/flutter/skills --skill flutter-add-widget-test