# How to Test Animations and Async UI Updates in Flutter: pump() vs pumpAndSettle()

> Master Flutter UI testing with pump() and pumpAndSettle(). Learn to test animations and async updates, advancing the clock for intermediate states or settling all pending actions.

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

---

**Use `pump()` with a specific `Duration` to advance the animation clock by exact increments for testing intermediate states, and use `pumpAndSettle()` to automatically wait for all pending animations, timers, and asynchronous UI updates to complete.**

The `WidgetTester` class drives the rendering pipeline in Flutter widget tests, offering precise control over frame advancement through two distinct APIs. When you need to test animations and async UI updates with pump vs pumpAndSettle, choosing the correct method depends on whether you require deterministic timing over individual frames or need to wait for the widget tree to reach a stable state. The flutter/skills repository demonstrates similar architectural patterns in its command-driven tooling, where synchronous steps and asynchronous coordination are cleanly separated across files like `tool/generator/lib/src/commands/base_skill_command.dart` and `tool/dart_skills_lint/lib/src/validator.dart`.

## Understanding pump() and pumpAndSettle()

### Deterministic Frame Control with pump()

The `pump()` method renders a new frame after a **single** tick of the event loop. You can optionally pass a `Duration` to simulate the passage of time, such as `await tester.pump(const Duration(milliseconds: 100));`. This method gives you explicit control over the animation clock, allowing you to stop at precise moments to verify interpolation values or UI states at specific time offsets.

### Automatic Settlement with pumpAndSettle()

In contrast, `pumpAndSettle()` repeatedly calls `pump()` until the widget tree reports that no more frames are scheduled **or** a timeout (default 10 seconds) is reached. This method effectively "waits" for all pending animations, microtasks, and layout passes to complete, making it ideal for testing `Future.delayed` operations or animations driven by a `TickerProvider` where the exact duration is unknown.

## When to Use Each Method

- **`pump(Duration)`** – Use this when you want **deterministic control over exact time steps**. This is essential for stepping through an animation frame-by-frame to assert intermediate states (e.g., verifying opacity is exactly 0.3 at 300ms into a fade) or when testing code that schedules a `Future` or `Timer` with a known delay.

- **`pumpAndSettle()`** – Use this when you want to wait for **all** animations to finish without manually counting frames. This is the correct choice when the UI performs asynchronous work (e.g., mocked network fetches) and you only care about the final settled state, or when the exact duration of the animation is driven by internal configuration.

## Code Examples

### Testing Mid-Animation States with pump()

When you need to verify values during an animation rather than just the final state, use explicit durations with `pump()`:

```dart
testWidgets('FadeTransition fades in over 1 second', (WidgetTester tester) async {
  await tester.pumpWidget(
    MaterialApp(
      home: FadeTransition(
        opacity: const AlwaysStoppedAnimation(0.0),
        child: const Text('Hello'),
      ),
    ),
  );

  // Verify initial opacity is 0.
  final fadeFinder = find.text('Hello');
  expect(tester.widget<FadeTransition>(find.byType(FadeTransition)).opacity.value, 0.0);

  // Advance 300ms into the animation.
  await tester.pump(const Duration(milliseconds: 300));

  // Now opacity should be roughly 0.3 (linear curve by default).
  expect(tester.widget<FadeTransition>(find.byType(FadeTransition)).opacity.value,
         closeTo(0.3, 0.01));
});

```

*Key points*: `pump(const Duration…)` moves the animation clock forward exactly 300ms, letting you assert an intermediate state.

### Waiting for Async Completion with pumpAndSettle()

When testing widgets that fetch data asynchronously, `pumpAndSettle()` handles the timing automatically:

```dart
testWidgets('Shows data after a mocked network delay', (WidgetTester tester) async {
  // The widget fetches data in initState and shows a CircularProgressIndicator
  // until the Future completes.
  await tester.pumpWidget(const MyDataFetchingWidget());

  // The spinner should be visible initially.
  expect(find.byType(CircularProgressIndicator), findsOneWidget);

  // Simulate the network delay (the widget internally uses Future.delayed).
  await tester.pumpAndSettle(); // Waits for the Future and the subsequent rebuild.

  // After settling, the spinner disappears and the data appears.
  expect(find.byType(CircularProgressIndicator), findsNothing);
  expect(find.text('Loaded data'), findsOneWidget);
});

```

*Key points*: `pumpAndSettle()` automatically handles the `Future.delayed` and the UI update, without needing to know the exact delay.

### Hybrid Approach for Complex Scenarios

For widgets with multiple animation phases, combine both methods for granular verification:

```dart
testWidgets('SnackBar disappears after its animation', (WidgetTester tester) async {
  await tester.pumpWidget(const MaterialApp(home: Scaffold(body: MySnackBarButton())));

  // Tap the button that shows a SnackBar.
  await tester.tap(find.byType(ElevatedButton));
  await tester.pump(); // Show the SnackBar.

  expect(find.byType(SnackBar), findsOneWidget);

  // The SnackBar slides in (animation) – advance half of the entrance duration.
  await tester.pump(const Duration(milliseconds: 150));

  // Verify the SnackBar is partially visible.
  // (You could check its position if needed.)

  // Now wait for the full lifecycle (entrance, display, exit).
  await tester.pumpAndSettle();

  // SnackBar should be gone.
  expect(find.byType(SnackBar), findsNothing);
});

```

*Key points*: The test uses `pump` for a deterministic halfway-point check, then `pumpAndSettle` to let the rest of the animation and dismissal run automatically.

## Common Pitfalls

### Timeout Errors in Infinite Animations

If a widget repeatedly schedules frames (e.g., an infinite animation), `pumpAndSettle()` will time out after 10 seconds. In such cases, you must fall back to explicit `pump()` calls with a bounded duration rather than waiting for settlement.

### Handling pumpAndSettle() Return Values

`pumpAndSettle()` returns a `bool` indicating completion status: it returns `true` if it timed out, `false` otherwise. Checking this return value can surface flaky tests early by distinguishing between incomplete animations and settled states.

### Microtask Flushing Behavior

`pump()` advances the frame **and** flushes microtasks. If you only need to flush a `Future` without a visible frame change, a zero-duration `await tester.pump();` is sufficient, as it processes microtasks without advancing the clock.

## Architectural Parallels in flutter/skills

The separation between synchronous stepping and asynchronous waiting in Flutter tests mirrors the architecture found in the flutter/skills repository. The file `tool/generator/lib/src/commands/base_skill_command.dart` implements command execution as deterministic, isolated steps analogous to individual `pump()` calls. Similarly, `tool/dart_skills_lint/lib/src/validator.dart` processes validation logic in discrete steps, reflecting the frame-by-frame control that `pump()` provides.

For asynchronous coordination, `tool/generator/lib/src/services/resource_fetcher_service.dart` performs async fetches that require waiting for completion—paralleling how `pumpAndSettle()` waits for pending `Future` objects. The `tool/dart_hooks/lib/src/dart_analyze_hook.dart` file demonstrates awaiting process completion with timeout handling, similar to `pumpAndSettle()` repeatedly pumping until no more work remains. Finally, `tool/dart_skills_lint/lib/src/rule_registry.dart` registers lint rules in a manner analogous to how Flutter registers animation callbacks that `pumpAndSettle()` waits for before completing.

## Summary

- Use `await tester.pump(Duration)` when you need to test animations and async UI updates with pump vs pumpAndSettle by controlling exact time increments and verifying intermediate states.
- Use `await tester.pumpAndSettle()` to automatically wait for all animations, timers, and async operations to complete when only the final settled state matters.
- Check the return value of `pumpAndSettle()` (`true` indicates timeout) to detect infinite animation loops and flaky tests early.
- Reference architectural patterns in `tool/generator/lib/src/commands/base_skill_command.dart` and `tool/dart_hooks/lib/src/dart_analyze_hook.dart` from the flutter/skills repository to understand the design philosophy separating synchronous steps from asynchronous coordination.

## Frequently Asked Questions

### Can I use pumpAndSettle() if my widget has an infinite loading animation?

No. `pumpAndSettle()` will throw a timeout error after 10 seconds because the animation continuously schedules new frames, never allowing the tree to settle. Instead, use `pump(const Duration(milliseconds: 100))` with explicit bounds to test specific snapshots of the animation.

### What is the default timeout for pumpAndSettle() and how do I change it?

The default timeout is 10 seconds. You can adjust this by passing a `Duration` to the `timeout` parameter, such as `await tester.pumpAndSettle(const Duration(seconds: 5))`.

### How do I test a widget that uses Future.delayed without hardcoding the duration?

Use `pumpAndSettle()` without arguments. It automatically advances time until the `Future` completes and the widget rebuilds, regardless of whether the delay is 500 milliseconds or 5 seconds.

### Does pump() run microtasks or just advance the clock?

`pump()` both advances the clock and flushes microtasks. A call to `await tester.pump()` with no duration argument specifically flushes microtasks without advancing time, which is useful for testing async logic that doesn't trigger visual updates but schedules `Future` resolution.