# How to Add Widget Tests Using WidgetTester and Finder in Flutter

> Learn to add widget tests in Flutter using WidgetTester and Finder. Discover how to build widget trees and assert UI state for fast, headless testing.

- Repository: [Flutter/skills](https://github.com/flutter/skills)
- Tags: how-to-guide
- Published: 2026-05-09

---

**The `flutter_test` package provides a `WidgetTester` object that builds widget trees, `Finder` instances that locate specific widgets, and `Matcher` values like `findsOneWidget` that assert UI state, enabling fast headless testing without emulators.**

To add widget tests using WidgetTester and Finder in Flutter, you utilize the testing framework built into the Flutter SDK. The **flutter/skills** repository provides a complete reference implementation in [`skills/flutter-add-widget-test/SKILL.md`](https://github.com/flutter/skills/blob/main/skills/flutter-add-widget-test/SKILL.md) that demonstrates how to verify widget behavior through a deterministic, step‑by‑step workflow. This method allows you to validate UI interactions, state changes, and animations in isolation.

## Setup and Configuration

Before writing tests, configure your project to include the testing framework.

Add `flutter_test` to the `dev_dependencies` section of your [`pubspec.yaml`](https://github.com/flutter/skills/blob/main/pubspec.yaml):

```yaml
dev_dependencies:
  flutter_test:
    sdk: flutter

```

Place all test files inside the `test/` directory at your project root, ensuring each filename ends with the `_test.dart` suffix. According to the source code at lines 21–24 in [`skills/flutter-add-widget-test/SKILL.md`](https://github.com/flutter/skills/blob/main/skills/flutter-add-widget-test/SKILL.md), this naming convention allows the test runner to automatically discover and execute your widget tests.

## Core Components: WidgetTester, Finder, and Matcher

The widget testing architecture relies on three cooperating classes introduced at lines 29–32 of the skill documentation:

- **`WidgetTester`** – Supplied as the `tester` parameter to every `testWidgets` callback, this object controls the test environment, builds widgets, and simulates user interactions.
- **`Finder`** – A class that searches the widget tree for specific criteria. Common factory methods include `find.text()`, `find.byType()`, `find.byKey()`, and `find.byWidget()`.
- **`Matcher`** – Functions that validate Finder results, such as `findsOneWidget` (exactly one match), `findsNothing` (zero matches), or `findsNWidgets(int count)`.

## The Widget Testing Workflow

The skill documentation outlines a nine‑item checklist (lines 38–45 in [`SKILL.md`](https://github.com/flutter/skills/blob/main/SKILL.md)) that governs every widget test:

1. Define a `testWidgets` function with a descriptive string and async callback.
2. Build the widget using `await tester.pumpWidget()`.
3. Create a **Finder** to locate specific elements in the tree.
4. Interact with the UI via methods like `tester.tap()`, `tester.enterText()`, or `tester.drag()`.
5. Trigger rebuilds with `tester.pump()` to process `setState` calls.
6. Wait for animations to complete using `tester.pumpAndSettle()` when necessary.
7. Assert expectations using `expect(finder, matcher)`.
8. Pump again if subsequent state changes occur.
9. Verify final state matches your requirements.

## Writing Your First Widget Test

A minimal widget test follows this structure:

```dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/my_widget.dart';

void main() {
  testWidgets('my widget renders correctly', (WidgetTester tester) async {
    // Build the widget inside a MaterialApp for context
    await tester.pumpWidget(const MaterialApp(home: MyWidget()));
    
    // Locate the text and verify it appears exactly once
    expect(find.text('Hello Flutter'), findsOneWidget);
  });
}

```

**Key implementation details:**
- The `testWidgets` function automatically supplies the **WidgetTester** instance as the `tester` parameter.
- `pumpWidget` renders the widget tree in a headless environment.
- `find.text()` creates a **Finder** that searches for `Text` widgets containing the specified string.
- `findsOneWidget` is a **Matcher** that asserts the Finder returned exactly one widget.

## Handling Interactions and Animations

Lines 52–60 of [`skills/flutter-add-widget-test/SKILL.md`](https://github.com/flutter/skills/blob/main/skills/flutter-add-widget-test/SKILL.md) detail interaction patterns for different scenarios:

**Static rendering** – Simply pump the widget and assert initial state.

**State changes** – After interactions like taps, call `await tester.pump()` to process `setState` triggers and rebuild the tree.

**Animations** – Use `await tester.pumpAndSettle()` to run all pending animations to completion before making assertions. This is essential for dismissible items, page transitions, or custom animation controllers.

**Text input** – Use `await tester.enterText(find.byType(TextField), 'input')` to simulate keyboard entry into input fields.

**Scrolling** – Use `tester.drag()` to simulate swipe gestures on scrollable widgets.

## Complete Example: Testing a TodoList Widget

The skill documentation provides a comprehensive example from lines 66–154 that demonstrates testing a `TodoList` widget with state management and animations:

```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 empty 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 to reflect state change
    await tester.pump();
    
    // 6. Verify item appears
    expect(find.text('Buy groceries'), findsOneWidget);
    
    // 7. Swipe to dismiss
    await tester.drag(find.byType(Dismissible), const Offset(500, 0));
    
    // 8. Wait for animation to finish
    await tester.pumpAndSettle();
    
    // 9. Verify removal
    expect(find.text('Buy groceries'), findsNothing);
  });
}

```

**Step‑by‑step breakdown:**
- **Step 1:** `pumpWidget` renders the `TodoList` inside the test environment.
- **Step 2:** `find.byType(ListTile)` combined with `findsNothing` confirms the list starts empty.
- **Steps 3–4:** `enterText` and `tap` simulate user input and button presses.
- **Step 5:** `pump()` processes the `setState` call that adds the todo item.
- **Step 6:** The **Matcher** confirms the new text appears in the tree.
- **Steps 7–8:** `drag` triggers the dismiss gesture, while `pumpAndSettle` waits for the dismiss animation to complete.
- **Step 9:** Final assertion confirms the item no longer exists.

## Summary

- **Configure** your project by placing tests in the `test/` folder with `_test.dart` suffixes and depending on `flutter_test`.
- **Build** widget trees using `WidgetTester.pumpWidget()` inside `testWidgets` callbacks.
- **Locate** widgets with **Finder** objects like `find.byType()`, `find.text()`, and `find.byKey()`.
- **Interact** using `tester.tap()`, `tester.enterText()`, and `tester.drag()`, then rebuild with `pump()` or `pumpAndSettle()`.
- **Assert** outcomes with **Matcher** values such as `findsOneWidget` or `findsNothing`.
- **Reference** the complete workflow and examples in [`skills/flutter-add-widget-test/SKILL.md`](https://github.com/flutter/skills/blob/main/skills/flutter-add-widget-test/SKILL.md) within the flutter/skills repository.

## Frequently Asked Questions

### What is the difference between `pump()` and `pumpAndSettle()`?

`pump()` schedules a frame and returns immediately after the build phase, which is sufficient for simple state changes. `pumpAndSettle()` repeatedly pumps frames until no more scheduled frames remain, making it essential for testing widgets with animations, transitions, or asynchronous operations that complete over multiple frames.

### How do I find a widget by its Key in a Flutter widget test?

Use `find.byKey(Key('my_key'))` to create a Finder that locates widgets matching a specific `Key` value. This approach is more resilient than searching by text or type when the widget hierarchy changes frequently. Pass this Finder to `expect()` with a Matcher like `findsOneWidget` to verify the widget exists.

### Can I test widget interactions like scrolling and typing?

Yes. `WidgetTester` provides `drag()` for scroll gestures, `enterText()` for text input, and `tap()` for button presses. According to the flutter/skills source code at lines 52–60, you should follow these interactions with `pump()` or `pumpAndSettle()` to ensure the widget tree reflects the updated state before running assertions.

### Where should I place widget test files in a Flutter project?

Place widget test files inside the `test/` directory at your project root, ensuring each filename ends with `_test.dart`. As documented at lines 21–24 in [`skills/flutter-add-widget-test/SKILL.md`](https://github.com/flutter/skills/blob/main/skills/flutter-add-widget-test/SKILL.md), the Flutter test runner automatically discovers files matching this pattern when you execute `flutter test`.