Testland
Browse all skills & agents

flutter-testing

Authors Flutter tests across the three-layer pyramid - unit (`flutter test`), widget (`testWidgets` + `WidgetTester`), integration (`integration_test` on simulator/emulator/device). Picks the right layer per change, mocks via `mockito` + `build_runner`, LCOV coverage, CI with the Flutter Action. Use when the app is Flutter and the team wants its first-party stack. For React Native use detox-testing; for black-box cross-platform use appium-testing; for YAML-declarative flows use maestro-flows.

Install with skills.sh (any agent)

npx skills add testland/qa --skill flutter-testing
View source

flutter-testing

Overview

Per flutter-testing-doc (opens in new window):

"Flutter uses a testing pyramid approach with three main categories":

  1. Unit Tests - single function / method / class; mocked dependencies; quick.
  2. Widget Tests - single widget; UI + lifecycle + interactions; quick.
  3. Integration Tests - full app or large sections; on real devices/emulators; highest confidence; slowest.

The framework ships first-party tooling for all three layers.

When to use

  • The app is Flutter (the framework's intended use case).
  • The team uses Dart and wants test-stack consistency with production code.

Step 1 - Install

Flutter ships with flutter_test (in the SDK). For mocks:

# pubspec.yaml
dev_dependencies:
  flutter_test:
    sdk: flutter
  integration_test:
    sdk: flutter
  mockito: ^5.4.4
  build_runner: ^2.4.13

Step 2 - Unit tests

Pure-Dart functions with mocked dependencies:

// test/cart_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/cart.dart';

void main() {
  group('Cart', () {
    test('addItem increments count', () {
      final cart = Cart();
      cart.addItem(Item(sku: 'BOOK-001', qty: 1));
      expect(cart.itemCount, 1);
    });

    test('addItem rejects negative qty', () {
      final cart = Cart();
      expect(
        () => cart.addItem(Item(sku: 'BOOK-001', qty: -1)),
        throwsA(isA<ArgumentError>()),
      );
    });
  });
}

Run:

flutter test test/cart_test.dart
flutter test                       # all tests
flutter test --coverage            # produces coverage/lcov.info

The LCOV output feeds lcov-analysis (in the qa-test-reporting plugin).

Step 3 - Widget tests

Per flutter-testing-doc (opens in new window), widget tests "verify the UI looks and interacts as expected":

// test/cart_screen_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/cart_screen.dart';

void main() {
  testWidgets('CartScreen shows item count', (WidgetTester tester) async {
    await tester.pumpWidget(MaterialApp(home: CartScreen(initialCount: 3)));

    expect(find.text('3 items'), findsOneWidget);
    expect(find.byKey(Key('add-to-cart-button')), findsOneWidget);
  });

  testWidgets('Tapping add button increments count', (WidgetTester tester) async {
    await tester.pumpWidget(MaterialApp(home: CartScreen(initialCount: 0)));

    await tester.tap(find.byKey(Key('add-to-cart-button')));
    await tester.pump();   // rebuild after state change

    expect(find.text('1 item'), findsOneWidget);
  });
}

tester.pumpWidget(...) mounts the widget tree. tester.pump() advances the frame; tester.pumpAndSettle() advances until no animations are pending.

Finders:

FinderUse
find.byKey(Key(...))By Key (preferred for stable lookups).
find.text("...")By visible text (translation-fragile).
find.byType(Widget)By widget type.
find.byIcon(Icons.x)By Material icon.
find.descendant(of:, matching:)Nested matching.

Step 4 - Integration tests

Per flutter-testing-doc (opens in new window), integration tests "test complete app or large app sections" on real devices.

// integration_test/checkout_flow_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:my_app/main.dart' as app;

void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();

  testWidgets('checkout flow', (WidgetTester tester) async {
    app.main();
    await tester.pumpAndSettle();

    await tester.tap(find.byKey(Key('login-button')));
    await tester.pumpAndSettle();

    await tester.enterText(find.byKey(Key('email-field')), 'qa-test@example.com');
    // ... etc.
  });
}

Run:

# On a connected device or simulator/emulator
flutter test integration_test/checkout_flow_test.dart

Step 5 - Mockito + build_runner

For mocks:

// test/cart_test.dart
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'cart_test.mocks.dart';   // generated

@GenerateMocks([CartRepo])
void main() {
  test('Cart loads from repo', () async {
    final mockRepo = MockCartRepo();
    when(mockRepo.getCart()).thenAnswer(
      (_) async => Cart(items: [Item(sku: 'BOOK-001', qty: 1)]),
    );

    final cart = await mockRepo.getCart();
    expect(cart.itemCount, 1);
    verify(mockRepo.getCart()).called(1);
  });
}

Generate the *.mocks.dart files:

dart run build_runner build --delete-conflicting-outputs

Step 6 - Coverage + reporting

flutter test --coverage           # writes coverage/lcov.info
genhtml coverage/lcov.info -o coverage/html   # human report

The LCOV file feeds the same parser other plugins use (lcov-analysis) for cross-language coverage aggregation.

Step 7 - CI integration

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: subosito/flutter-action@v2
        with:
          channel: stable
      - run: flutter pub get
      - run: dart run build_runner build --delete-conflicting-outputs
      - run: flutter test --coverage
      - uses: codecov/codecov-action@v5
        with:
          files: coverage/lcov.info

  integration-android:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: subosito/flutter-action@v2
      - uses: reactivecircus/android-emulator-runner@v2
        with:
          api-level: 34
          script: flutter test integration_test/

Per flutter-testing-doc (opens in new window), Flutter supports CI integration via "Fastlane / Travis CI / Cirrus CI / Codemagic / Bitrise / Appcircle / Patrol (for native platform interactions)."

Anti-patterns

Anti-patternWhy it failsFix
All tests as integration testsSlow; fragile; hides per-layer issues per the pyramid.Many unit + widget; few integration (Step 1 pyramid).
find.text("...") for translatable stringsTranslation breaks tests.find.byKey(Key("...")) (Step 3).
tester.pump() (single frame) for animationsAnimation isn't done; assertion fires too early.tester.pumpAndSettle() for animations.
Skipping IntegrationTestWidgetsFlutterBinding.ensureInitialized()Integration test won't bind; runtime errors.Add as first line in main() (Step 4).
Mock without @GenerateMocks (manual mock classes)Tedious; drift when the SUT interface changes.Use @GenerateMocks + build_runner (Step 5).
Coverage from integration tests onlyIntegration coverage is sparse; misses unit-level branches.Coverage from flutter test (which runs unit + widget).

Limitations

  • flutter drive deprecated for new projects. Use integration_test package (modern; Step 4).
  • Native platform features need extra packages. For things like permissions / camera / push notifications, use patrol or per-platform native integration.
  • Widget tests use a fake Stub for MediaQuery etc. Surface varies vs production; widget tests don't cover layout/font issues that need a real renderer.
  • Mockito's null safety story. Generation handles it; manual mocks are awkward.

References

  • ft (opens in new window) - Flutter testing pyramid: unit / widget / integration; trade-off matrix (confidence, maintenance, deps, speed); CI integration list; flutter test / flutter drive commands.
  • xcuitest-suite, espresso-suite, detox-testing - alternative framework wrappers when the app isn't Flutter.
  • lcov-analysis - downstream consumer of flutter test --coverage.

Related skills

appium-testing

Wires Appium for cross-platform mobile UI automation - uses the WebDriver protocol, picks a driver per platform (XCUITest for iOS, UiAutomator2 / Espresso for Android, Mac2 for macOS, Windows for desktop), authors tests in JS / Python / Java / Ruby / .NET, configures `desiredCapabilities`, runs against simulators / emulators / device farms. Use when a single test suite must cover both iOS and Android, or when the team's stack is multi-platform (iOS + Android + Mac + Windows).

detox-testing

Authors React Native E2E tests with Detox (Wix) - gray-box architecture (runs in-process with the app), `element(by.id|by.text|by.label)` matchers, `waitFor()` for explicit sync beyond Detox's automatic async tracking, Jest runner. Use when the app is React Native and speed matters. For Flutter use flutter-testing; for black-box cross-platform use appium-testing; for YAML-declarative flows use maestro-flows; for non-RN native use xcuitest-suite or espresso-suite.

espresso-suite

Authors Espresso UI tests for Android - uses `onView(withId(...)).perform(...).check(matches(...))`, leans on Espresso's automatic synchronization (no `Thread.sleep`), wires `IdlingResource` for app-specific async, runs via `./gradlew connectedAndroidTest` and parses the JUnit XML output. Use when an Android app needs UI tests in Google's first-party framework.

maestro-flows

Authors Maestro YAML flow files (`.maestro/*.yaml`) for mobile + web UI automation: declarative `tapOn`, `inputText`, `assertVisible`, `swipe`, supported targets (iOS, Android, Flutter, React Native, web), nested flow imports, JavaScript hooks for complex conditions. Use when the team has already chosen Maestro, is coming from an existing `.maestro/` directory, or explicitly wants YAML-declarative tests readable by non-engineers without a compile step. For framework selection or authoring tests in XCUITest / Espresso / Detox / Appium / Flutter, use a mobile driver-selection or per-flow mobile test-authoring step instead.

mobile-a11y-test-author

Authors native mobile accessibility tests covering iOS (Accessibility Inspector, XCUITest `performAccessibilityAudit()` introduced in iOS 17, VoiceOver label/trait/hint verification) and Android (Espresso `AccessibilityChecks.enable()`, Accessibility Scanner, TalkBack traversal, `contentDescription` labelling) with WCAG-aligned checks for element labels, 44pt/48dp touch targets, contrast ratios, and focus order. Use when an iOS or Android app needs automated and manual accessibility test coverage beyond what `xcuitest-suite` or `espresso-suite` provide.

mobile-device-matrix-toolkit

Dispatches mobile UI test runs across a 3-tier device matrix (smoke per-PR, regression per-merge, full farm at release) to control CI cost: generates per-target Appium capability configs from a central YAML, parallelises via GitHub Actions matrix strategy, and aggregates JUnit XML into a cross-device pass/fail table. Use when deciding which iOS / Android devices and OS versions to run tests on and at which stage (smoke / regression / full farm), not how to configure a specific test framework (for that, use xcuitest-suite, espresso-suite, etc.).

mobile-web-emulation-runner

Builds a workflow to run web E2E tests under mobile viewports + DPRs (device pixel ratios): Playwright's `devices` catalog (iPhone 15, Pixel 7), suite run per-device as matrix shards, per-device screenshots, mobile assertions (`.tap()`, viewport-conditional layout). Use when a responsive web app needs mobile-breakpoint regression without a real-device farm. Mobile WEB only - for native apps use appium-testing, detox-testing, or flutter-testing; for cross-shard aggregation use mobile-device-matrix-toolkit; for gesture sequences use touch-gesture-tester.

mobile-web-perf-budget

Pure-reference skill for mobile-web performance budgets - Core Web Vitals at the 75th percentile mobile (LCP ≤2.5s, INP ≤200ms, CLS ≤0.1; FID retired March 2024 in favor of INP), Lighthouse mobile profile config, per-route resource budgets (JS bundle, image weight, font load). Use as the team's reference for "what should the mobile perf gate enforce" - paired with `lighthouse-perf` (the runner) and `lighthouse-budget-author` (the per-route author).

touch-gesture-tester

Verifies touch-gesture handlers (tap, double-tap, long-press, swipe, pinch, rotate, pan) work as expected under both mobile-emulation (Playwright) and native (XCUITest / Espresso / Detox) - distinguishes "mouse click handler also fires on tap" from "real touch event fired with correct properties." Use when the app has bespoke gesture handlers (custom carousels, sliders, drag-drop, pull-to-refresh) and the team needs targeted gesture verification beyond generic UI assertions.

xcuitest-suite

Authors XCUIest UI tests for iOS / iPadOS / tvOS - uses the three-class XCUIApplication / XCUIElement / XCUIElementQuery pattern, sets accessibility identifiers on production code, runs via `xcodebuild test` with destination, parses the `xcresult` bundle. Use when an iOS app needs UI tests in Apple's first-party framework (no external runtime; native to Xcode).