Comprehensive Testing Guide for Flutter: Unit, Widget, E2E & Agentic Workflows
A deep dive into unit testing, E2E integration testing, and AI agentic testing workflows in a Flutter Riverpod and Drift codebase.
Published on • September 24, 2026
AI Assistant

This guide explains how unit testing, End-to-End (E2E) integration testing, and agentic AI workflows are implemented in a Flutter codebase (todo_riverpod), along with step-by-step instructions on how to execute and verify them.
Architecture Overview
The todo_riverpod application follows a multi-layered testing strategy:
flowchart TD
subgraph "Unit & Integration Tests (test/)"
DB[Database Layer Tests] -->|In-memory SQLite| AppDB[AppDatabase]
Actions[TodoActions Tests] -->|ProviderContainer Override| DB
State[ThemeModeState Tests] -->|Mocked SharedPreferences| Settings[SettingsProvider]
Stream[TodoListProvider Tests] -->|Stream Assertions| StreamSub[ProviderSubscription]
Router[AppRouter Tests] -->|GoRouter Navigation| RouterView[Screen Widgets]
end
subgraph "E2E Tests (integration_test/ & test_driver/)"
E2E[todo_app_test.dart] -->|WidgetTester & Driver| App[Full App Workflow]
end
1. Unit & Widget Testing Strategy (test/)
Unit and component tests verify individual layers in isolation without requiring physical devices or desktop application instances.
1.1 Database Tests (test/database/app_database_test.dart)
- Isolation: Uses
AppDatabase.forTesting(NativeDatabase.memory())to run SQLite completely in-memory. - Coverage:
- CRUD: Validates
insertTodo,getTodoById,updateTodo(replace), anddeleteTodo. - Reactivity: Verifies
watchAllTodos()andwatchTodoById()emit updated values on insert, update, and deletion. - Schema Constraints: Tests title length boundary rules (rejects empty string
""and titles> 200characters, accepts200characters).
- CRUD: Validates
1.2 Provider Actions Tests (test/providers/todo_actions_test.dart)
- Isolation: Evaluates
TodoActionsusing aProviderContainerwithappDatabaseProvideroverridden by an in-memory DB instance. - Coverage:
addTodo: Verifies creation with defaultisCompleted = falseand generated timestamps.updateTodo: EnsuresisCompletedandcreatedAtare preserved whileupdatedAtis updated.toggleCompleted: Tests boolean inversion (false➔true➔false) and error handling on invalid IDs.deleteTodo: Tests row removal and idempotency for non-existent IDs.
1.3 Settings Provider Tests (test/providers/settings_provider_test.dart)
- Isolation: Uses
SharedPreferences.setMockInitialValues({})and overridessharedPreferencesProvider. - Coverage:
- Defaulting to
ThemeMode.systemwhen unset. - Reading persisted integer values from key
'theme_mode'. - Persistence round-tripping across
ProviderContainerteardown and rebuild for allThemeModeenum options (system,light,dark).
- Defaulting to
1.4 Todo List Provider Tests (test/providers/todo_list_provider_test.dart)
- Isolation: Subscribes to
todoListProviderstream usingProviderContainer.listen. - Coverage:
- Initial
AsyncValue.data([])emission. - Stream reactivity upon database operations (
addTodo,toggleCompleted,deleteTodo).
- Initial
1.5 Router Tests (test/router/app_router_test.dart)
- Isolation: Mounts
TodoAppwith GoRouter within widget test framework. - Coverage:
/loadsTodoListScreen./addloadsAddEditTodoScreenin creation mode./edit/:idpre-fills details when given a valid numeric ID./edit/abcand invalid IDs fall back safely toTodoListScreen./settingsloadsSettingsScreen.
2. End-to-End (E2E) Integration Testing (integration_test/ & test_driver/)
E2E tests simulate user interaction across the entire stack using integration_test and test_driver.
2.1 Test Driver Configuration (test_driver/integration_test.dart)
Exposes the standard entry point required by flutter drive:
import 'package:integration_test/integration_test_driver.dart';
Future<void> main() => integrationDriver();
2.2 E2E Test Suite (integration_test/todo_app_test.dart)
Contains 9 automated scenarios verifying full user journeys:
| # | Scenario | Verification |
|---|---|---|
| 1 | Empty State | Verifies "No todos yet" placeholder on initial launch. |
| 2 | Add Todo | Taps FAB ➔ fills title/description ➔ taps Add ➔ verifies card on home screen. |
| 3 | Form Validation | Taps Add on empty form ➔ verifies error text "Please enter a title" and prevents navigation. |
| 4 | Toggle Completed | Taps item checkbox ➔ verifies TextDecoration.lineThrough and greyed styling ➔ toggles back. |
| 5 | Pre-filled Edit | Opens popup menu ➔ taps Edit ➔ verifies fields pre-filled ➔ modifies text ➔ verifies update. |
| 6 | Delete Flow | Opens popup menu ➔ taps Delete ➔ tests Cancel (todo retained) ➔ repeats & confirms Delete (todo removed). |
| 7 | Settings Navigation | Taps header gear icon ➔ verifies presence of 3 theme radio tiles (System Default, Light, Dark). |
| 8 | Theme Change | Selects Dark ➔ verifies MaterialApp.themeMode converts to ThemeMode.dark. |
| 9 | State Persistence | Adds todo & sets Dark mode ➔ unmounts app ➔ relaunches ➔ verifies todo and theme persist. |
3. Agentic Testing Workflows: Using Coding Agents & Flutter Skills
AI coding agents leverage structured agent skills and MCP tools to autonomously analyze codebases, generate unit/widget test suites, and author E2E integration tests.
3.1 Overview of Flutter Testing Agent Skills
Specialized Flutter agent skills provide structured automation:
| Agent Skill | Purpose & Usage |
|---|---|
dart-add-unit-test | Creates unit tests for Dart functions, Drift database models, and Riverpod state logic using package:test / flutter_test. |
flutter-add-widget-test | Authors component-level widget tests using WidgetTester to verify UI rendering, layout, and user interactions. |
dart-generate-test-mocks | Generates type-safe mock objects using package:mockito and build_runner for external services or dependencies. |
flutter-add-integration-test | Explores live UI via Flutter MCP, verifies widget trees, and synthesizes complete E2E integration tests. |
dart-run-static-analysis | Executes dart analyze and applies mechanical fixes via dart fix --apply. |
3.2 Using a Coding Agent to Create Unit & Widget Test Cases
When instructed to create unit or widget test cases for a new or existing feature, the coding agent follows a structured workflow:
flowchart TD
A[Analyze Source Code & Dependencies] --> B[Identify Test Isolation Strategy]
B --> C[Generate Mocks if needed]
C --> D[Invoke dart-add-unit-test or flutter-add-widget-test Skill]
D --> E[Write Test Suite in test/]
E --> F[Run flutter test & dart analyze Verification Loop]
-
Code Analysis & Context Gathering:
- The agent inspects the target source file (e.g., database DAO, Riverpod provider, or screen widget) using search and read tools.
- It identifies key edge cases, validation logic, state changes, and expected stream emissions.
-
Selecting the Isolation Strategy:
- Database: Uses
AppDatabase.forTesting(NativeDatabase.memory())for zero-side-effect SQLite operations. - Riverpod Providers: Instantiates a
ProviderContainerwith overridden dependencies (e.g., overridingappDatabaseProviderorsharedPreferencesProvider). - Widget Components: Wraps widgets in
MaterialAppor RiverpodProviderScopewithintester.pumpWidget().
- Database: Uses
-
Generating Test Cases:
- Applies the
dart-add-unit-testskill to format assertions, group test cases logically withgroup(), and handle async streams or futures. - Applies the
flutter-add-widget-testskill to locate widgets viafind.byKey,find.byType, orfind.text, simulate gestures (tester.tap,tester.enterText), and rebuild the tree usingtester.pumpAndSettle().
- Applies the
-
Self-Verification & Refinement:
- Runs
flutter test test/<file>_test.dartto verify pass status. - Runs
dart analyze lib/ test/to verify zero static analysis errors.
- Runs
3.3 Creating Integration Tests with flutter-add-integration-test Skill
The flutter-add-integration-test skill guides agents through interactive UI exploration and test generation across 4 key phases:
flowchart TB
A[1. App Setup & Widget Keys] --> B[2. MCP UI Exploration]
B --> C[3. Test Synthesis]
C --> D[4. Execution & Feedback Loop]
D -->|Failure / Timeout| C
D -->|Pass| E[Verification Complete]
Phase 1: App Setup & Key Assignment
- The agent ensures
integration_testandflutter_testdependencies are present inpubspec.yaml. - The agent assigns unique
ValueKeyidentifiers to target interactive elements (e.g.,ValueKey('add_todo_fab'),ValueKey('todo_title_input')). - If required, the agent enables
enableFlutterDriverExtension()inlib/main_test.dart.
Phase 2: Interactive Exploration via Dart/Flutter MCP
- The agent launches the app using
launch_appto retrieve the Dart Tooling Daemon (DTD) connection URI. - It inspects the live widget tree with
get_widget_treeto map visible keys, widget types, and semantic labels. - It tests live interaction sequences (
tap,enter_text,scroll) using MCP tools to confirm user flow paths before writing code.
Phase 3: Synthesizing Integration Test Suites
-
The agent creates or updates test scripts under
integration_test/<feature>_test.dart. -
Structure template synthesized by agent:
import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:todo_riverpod/main.dart' as app; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); group('E2E Feature Test', () { testWidgets('Complete user workflow', (WidgetTester tester) async { app.main(); await tester.pumpAndSettle(); // 1. Interact with UI final fab = find.byKey(const ValueKey('add_todo_fab')); await tester.tap(fab); await tester.pumpAndSettle(); // 2. Form input await tester.enterText(find.byKey(const ValueKey('title_input')), 'New Task'); await tester.tap(find.byKey(const ValueKey('save_button'))); await tester.pumpAndSettle(); // 3. Verify outcome expect(find.text('New Task'), findsOneWidget); }); }); } -
Also creates the companion host driver script at
test_driver/integration_test.dart:import 'package:integration_test/integration_test_driver.dart'; Future<void> main() => integrationDriver();
Phase 4: Execution & Feedback Loop
-
The agent executes tests using desktop or driver target modes:
flutter test -d windows integration_test/todo_app_test.dart -
Error Recovery Strategies Applied by Agent:
PumpAndSettleTimedOutException: ReplacepumpAndSettle()with discretepump(Duration(...))calls if infinite progress indicators or repeating timers exist.StateError / Bad state: No element: Ensure off-screen list items are scrolled into view withtester.scrollUntilVisible().- Stale database state: Inject test tearDown/setUp database reset hooks.
3.4 Example Prompts to Direct the Coding Agent
Below are practical prompts you can use to instruct a coding agent to build test cases using these skills:
Prompt 1: Generate Unit Tests for a Riverpod Provider
“Use the
dart-add-unit-testskill to write comprehensive unit tests forTodoActionsprovider intest/providers/todo_actions_test.dart. Test adding, editing, toggling completion status, and deleting todos using an in-memory Drift database.”
Prompt 2: Generate Widget Tests for a UI Component
“Use the
flutter-add-widget-testskill to create widget tests forAddEditTodoScreenintest/screens/add_edit_todo_screen_test.dart. Test form validation when title is empty, pre-filling text fields during edit mode, and tapping the save button.”
Prompt 3: Create an E2E Integration Test using flutter-add-integration-test
“Use the
flutter-add-integration-testskill to explore the app via Flutter MCP tools and create an E2E integration test inintegration_test/settings_theme_test.dart. Test navigating to settings, changing theme mode to Dark, and asserting that theme state persists after relaunching.”
4. How to Run & Verify Tests
4.1 Static Analysis & Formatting Checks
Run these commands after making changes to verify code formatting and static analysis rules:
# Run static analysis
dart analyze lib/ test/ integration_test/ test_driver/
# Verify formatting
dart format --output=none --set-exit-if-changed lib/ test/ integration_test/ test_driver/
4.2 Running Unit & Widget Tests
Execute all unit and widget tests headlessly:
flutter test
To run a specific test file:
flutter test test/database/app_database_test.dart
4.3 Running E2E Integration Tests
Option A: Running via flutter test (Widget/Target Mode)
You can run integration tests directly against your desktop platform target (e.g., Windows):
flutter test -d windows integration_test/todo_app_test.dart
Option B: Running via flutter drive (Full Flutter Driver Mode)
To run via the test_driver integration runner:
flutter drive --driver=test_driver/integration_test.dart --target=integration_test/todo_app_test.dart -d windows
5. Code Generation Reminder
If you modify schema models or Riverpod providers marked with @riverpod or @DriftDatabase, remember to regenerate code before testing:
dart run build_runner build --delete-conflicting-outputs