Testing
Irbisa · cheatsheetSeptember 13, 2026

Testing

Unit tests, widget tests, integration tests, mocking

Middle Developer20 itemscompressed for a skim
  1. 01

    What are the three types of tests in Flutter and when to use each?

    Medium

    Flutter has three test layers that trade speed against confidence: unit, widget, and integration.

    // ── Unit Test ─────────────────────────────────────
    import 'package:flutter_test/flutter_test.dart';
    import 'package:mocktail/mocktail.dart';
    
    class MockUserRepo extends Mock implements UserRepository {}
    …
  2. 02

    How do you write golden tests in Flutter?

    Medium

    Golden tests (snapshot tests) compare a widget's rendered output pixel-by-pixel against a saved "golden" reference image.

    import 'package:flutter_test/flutter_test.dart';
    import 'package:flutter/material.dart';
    
    void main() {
      // ── Basic golden test ────────────────────────────
      testWidgets('ProductCard golden', (tester) async {
    …
  3. 03

    How do you test widgets that depend on InheritedWidgets (Theme, MediaQuery, etc.)?

    Medium

    A widget reading Theme, MediaQuery or Localizations throws in a test unless a scope above it provides them.

    import 'package:flutter/material.dart';
    import 'package:flutter_test/flutter_test.dart';
    
    // ── One helper, used by every test in the suite ──
    Future<void> pumpApp(WidgetTester tester, Widget child, {ThemeData? theme}) {
      return tester.pumpWidget(MaterialApp(
    …
  4. 04

    How do you choose between mocktail and mockito for mocking dependencies?

    Medium

    Both let you fake classes for tests; the choice mostly comes down to ergonomics and null safety.

    // pubspec.dev: mocktail: ^1.0.0
    import 'package:mocktail/mocktail.dart';
    import 'package:flutter_test/flutter_test.dart';
    
    class MockUserApi extends Mock implements UserApi {}
    …
  5. 05

    What's the difference between pump, pumpAndSettle, pumpFrames, and runAsync in widget tests?

    Medium

    Widget tests run on a fake clock, so a five-second delay in your code costs the test nothing — you advance time yourself.

    testWidgets('animation reaches end', (tester) async {
      await tester.pumpWidget(MaterialApp(home: const _Animated()));
    
      // ✅ Step through animation frame-by-frame
      for (var t = Duration.zero; t < const Duration(milliseconds: 600); t += const Duration(milliseconds: 16)) {
        await tester.pump(const Duration(milliseconds: 16));
    …
  6. 06

    How do you write integration tests with integration_test or patrol?

    Hard

    Integration tests drive the whole app on a real device or emulator, with only the network faked at the boundary.

    import 'package:integration_test/integration_test.dart';
    import 'package:my_app/main.dart' as app;
    
    void main() {                       // integration_test/checkout_flow_test.dart
      IntegrationTestWidgetsFlutterBinding.ensureInitialized();
    …
  7. 07

    How do you mock HTTP/Dio in tests without real network calls?

    Medium

    Real HTTP is forbidden in CI — it is flaky, slow, and depends on a staging environment being up.

    // ── 1) Fake repository — the cleanest ──
    class FakeUsersRepo implements UsersRepository {
      @override Future<User> byId(int id) async => User(id: id, name: 'Ada');
      @override Future<List<User>> all({int page = 1}) async =>
          List.generate(3, (i) => User(id: i, name: 'u$i'));
    }
    …
  8. 08

    How do you measure code coverage and gate it in CI?

    Medium

    Coverage tells you which lines of source ran during tests.

    # .github/workflows/test.yml
    name: Test + Coverage
    on: [pull_request]
    
    jobs:
      test:
    …
  9. 09

    How do you test a screen that uses Riverpod or BLoC?

    Medium

    Both libraries let you swap the dependency layer for fakes and then assert either the emitted state or the rendered UI.

    // ── Riverpod — unit test with ProviderContainer ──
    import 'package:flutter_riverpod/flutter_riverpod.dart';
    import 'package:flutter_test/flutter_test.dart';
    import 'package:mocktail/mocktail.dart';
    
    class MockApi extends Mock implements ApiClient {}
    …
  10. 10

    In a widget test, how do you prove that tapping a button navigated to the right screen?

    Medium

    Prefer asserting that the destination is on screen after the tap, and fall back to a mock NavigatorObserver when the destination is too heavy to build.

    testWidgets('cart button opens checkout', (tester) async {
      await tester.pumpWidget(MaterialApp(
        home: const CartPage(),
        routes: {'/checkout': (_) => const CheckoutPage()},
      ));
    …
  11. 11

    A widget under test calls a plugin and the test throws MissingPluginException. What are your options?

    Hard

    There is no platform behind a Flutter test, so every channel call has to be answered by a fake — either at the plugin's Dart interface or at the binary messenger.

    void main() {
      TestWidgetsFlutterBinding.ensureInitialized();
    
      const channel = MethodChannel('com.example/battery');
      final messenger =
          TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger;
    …
  12. 12

    How do you test a debounce, a retry with backoff, or a poll without making the suite slow?

    Medium

    Never sleep in a test — run the code against a fake clock and move time forward yourself.

    test('debounce emits once after the quiet period', () {
      fakeAsync((async) {
        final seen = <String>[];
        final search = Debouncer(const Duration(milliseconds: 300));
    
        search.run(() => seen.add('a'));
    …
  13. 13

    A widget test fails with "Found 2 widgets with text Save" — how do you narrow the finder without reaching for .first?

    Medium

    Scope the finder to the subtree or the widget you mean, because tester.tap and findsOneWidget both demand exactly one match and an index just follows whatever the layout does next.

    testWidgets('confirming the dialog saves the note', (tester) async {
      await tester.pumpWidget(const MaterialApp(home: NoteEditor()));
      await tester.tap(find.byTooltip('Edit'));
      await tester.pumpAndSettle();
    
      // ❌ throws: the AppBar title and the dialog button both say 'Save'
    …
  14. 14

    A widget test taps the 40th row of a ListView and fails with "Nothing found", but the screen works fine in the app — what is happening?

    Medium

    ListView.builder only builds what fits the 800x600 test surface plus its cache extent, so row 40 is not in the element tree at all and no finder can see it — the test has to scroll, exactly like a user.

    testWidgets('tapping a far-down row opens it', (tester) async {
      await tester.pumpWidget(const MaterialApp(home: OrdersPage()));
    
      // ❌ nothing found: ListView.builder never built row 40
      // await tester.tap(find.text('Order 40'));
    …
  15. 15

    Golden tests have made every local flutter test run slow — how do you keep them out of the fast loop but still run them in CI?

    Medium

    Tag them and declare the tags in dart_test.yaml, then select tags on the command line — flutter test runs on package:test's runner and reads that file from the package root.

    # dart_test.yaml — package root, read by `flutter test`
    
    tags:
      golden:          # renders and diffs images: slower than a unit test
        timeout: 2x    # 2x the 30s default
      slow:
    …
  16. 16

    You renamed a private method and thirty tests went red without a single behaviour change — what were those tests doing wrong?

    Medium

    They asserted on how the code works instead of what it does, so a refactor — the exact thing tests are supposed to make safe — broke them.

    // ❌ Brittle: asserts the interaction and the tree shape
    testWidgets('loads the cart', (tester) async {
      final repo = MockCartRepo();
      when(() => repo.fetchPage(1)).thenAnswer((_) async => [item]);
    
      await tester.pumpWidget(MaterialApp(home: CartPage(repo: repo)));
    …
  17. 17

    How do you assert that a Cubit emits loading and then data, without sprinkling delays and hoping the timing works out?

    Hard

    Attach the expectation with expectLater and a stream matcher before you trigger the work, then await it — the matcher consumes events as they arrive, so nothing depends on how long the work takes.

    test('loads the profile', () async {
      final cubit = ProfileCubit(FakeProfileRepo());
    
      // ✅ subscribe first, then act — no delays anywhere
      final expectation = expectLater(
        cubit.stream,
    …
  18. 18

    The suite is green on your machine and fails one random test per CI run — how do you actually track that down?

    Hard

    Suspect shared state before timing: files run in parallel isolates, but every test inside a file shares the same globals, so the first candidate is something a previous test left behind.

    // ❌ Leaks into every later test in the file
    void main() {
      setUpAll(() {
        getIt.registerSingleton<CartRepo>(FakeCartRepo());   // built once, mutated by all
      });
    …
  19. 19

    Your widget throws inside build when the API returns null, and the test dies with that exception — how do you assert on it instead?

    Hard

    Claim it with tester.takeException(): the test binding records every exception the framework caught and rethrows anything still unclaimed when the test ends.

    testWidgets('a malformed payload surfaces as a FormatException', (tester) async {
      await tester.pumpWidget(
        MaterialApp(home: PriceTag(raw: 'not-a-number')),
      );
    
      // The framework already caught it; claim it or the test fails at the end
    …
  20. 20

    How do you catch an unlabelled icon button or a 40dp tap target in the test suite instead of in a screen-reader review?

    Hard

    flutter_test ships accessibility guideline matchers, but the semantics tree is built lazily, so a test has to turn it on with tester.ensureSemantics() before any of them mean anything.

    testWidgets('checkout screen meets the accessibility guidelines', (tester) async {
      final handle = tester.ensureSemantics();     // without this the tree is empty
      addTearDown(handle.dispose);
    
      await tester.pumpWidget(
        MaterialApp(theme: appTheme, home: const CheckoutPage()),
    …