Irbisa · Cheatsheet

Testing

Unit tests, widget tests, integration tests, mocking

Middle Developer · 25 questions

  1. 01

    What are the three types of tests in Flutter, and when do you use each?

    Unit tests check one function or class, widget tests render one widget or screen without a device, and integration tests drive the whole app on an emulator or phone.

  2. 02

    How do you structure a unit test in Dart: test, group, setUp/tearDown and expect?

    A test file has a main() with test('description', body) calls grouped by group; setUp runs before every test in its group, tearDown after each, and every test asserts with expect(actual, matcher).

    import 'package:flutter_test/flutter_test.dart'; void main() {  group('Cart', () {    late Cart cart;…
  3. 03

    How do you write a widget test: pumpWidget, finders, tester.tap and matchers?

    testWidgets gives you a WidgetTester: build the widget with pumpWidget, locate elements with find, act with tap or enterText, call pump to rebuild, and assert with expect(finder, findsOneWidget).

    testWidgets('counter increments when + is tapped', (tester) async {  await tester.pumpWidget(const MaterialApp(home: CounterPage()));   expect(find.text('0'), findsOneWidget);  expect(find.text('1'), findsNothing);…
  4. 04

    What is the difference between a mock, a stub and a fake, and when do you hand-write a fake instead of using mocktail?

    A stub returns canned answers, a mock also records calls so you can verify them, and a fake is a small working implementation such as an in-memory repository.

    class MockAnalytics extends Mock implements Analytics {} class InMemoryCartRepo implements CartRepo {  final _items = <Item>[];   @override…
  5. 05

    This mocktail test crashes with type 'Null' is not a subtype of type 'Future<void>': why, and what is the fix?

    class MockAnalytics extends Mock implements Analytics {} test('checkout logs a purchase', () async {  final analytics = MockAnalytics();  final cart = Cart(analytics);   await cart.checkout(); // calls analytics.logPurchase(42)   verify(() => analytics.logPurchase(42)).called(1);});

    logPurchase was never stubbed, and an unstubbed mocktail method returns null, which cannot be returned where a non-nullable Future<void> is declared.

  6. 06

    Find the bug: this mocktail test fails with No matching calls, although buyNow really sends Order('p1', 1).

    class Order {  Order(this.productId, this.qty);  final String productId;  final int qty;} class MockOrderApi extends Mock implements OrderApi {} test('Buy now sends one order for one item', () async {  registerFallbackValue(Order('', 0));  final api = MockOrderApi();  when(() => api.send(any())).thenAnswer((_) async {});  await Checkout(api).buyNow('p1'); // sends one Order   verify(() => api.send(Order('p1', 1))).called(1);});

    Order has no value equality, so the Order('p1', 1) built inside verify is a different object from the one buyNow sent.

  7. 07

    How do you test a widget that depends on Theme, MediaQuery or Localizations?

    Provide the scope the widget reads from, usually by wrapping it in a MaterialApp: with nothing above it, MediaQuery.of(context) throws and AppLocalizations.of(context)! crashes on null.

    extension PumpApp on WidgetTester {  Future<void> pumpApp(Widget child, {Locale locale = const Locale('en')}) {    return pumpWidget(MaterialApp(      locale: locale,      localizationsDelegates: AppLocalizations.localizationsDelegates,      supportedLocales: AppLocalizations.supportedLocales,…
  8. 08

    What is the difference between pump, pumpAndSettle, pumpFrames and runAsync in a widget test?

    Widget tests run on a fake clock: pump renders one frame and can move time forward, pumpAndSettle pumps until no frame is scheduled, pumpFrames steps through a fixed span, and runAsync escapes to the real clock for real I/O.

    testWidgets('promo fades in over 300 ms', (tester) async {  await tester.pumpWidget(const MaterialApp(home: PromoPage()));  await tester.tap(find.text('Show'));  await tester.pump(); // starts the animation   FadeTransition fade() =>…
  9. 09

    This widget test fails with pumpAndSettle timed out: what is going on, and how do you fix it?

    testWidgets('shows a spinner while the profile loads', (tester) async {  final pending = Completer<User>();  final repo = FakeRepo(onLoad: () => pending.future);   await tester.pumpWidget(MaterialApp(home: ProfilePage(repo: repo)));  await tester.pumpAndSettle();   expect(find.byType(CircularProgressIndicator), findsOneWidget);});

    An indeterminate CircularProgressIndicator schedules a new frame forever, so pumpAndSettle never sees an idle frame and gives up after ten minutes of fake time.

  10. 10

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

    Scope the finder to the subtree or the widget you actually mean.

    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();   // wrong: the AppBar title and the dialog button both say 'Save'…
  11. 11

    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?

    ListView.builder only builds the rows that fit the 800×600 test surface plus a small cache, so row 40 does not exist in the tree and no finder can see it.

    testWidgets('tapping a far-down order opens it', (tester) async {  await tester.pumpWidget(const MaterialApp(home: OrdersPage()));   // wrong: row 40 was never built, so the finder matches nothing  // await tester.tap(find.text('Order 40'));…
  12. 12

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

    Assert that the destination is on screen after the tap: pump a MaterialApp with your routes, tap, pumpAndSettle to finish the transition, then expect(find.byType(CheckoutPage), findsOneWidget).

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

    How do you test async code and streams: await, expectLater and emitsInOrder?

    Make the test body async and await futures directly; for a stream, attach expectLater(stream, emitsInOrder([...])) before triggering the work, then await it.

    test('emits loading, then the profile', () async {  final cubit = ProfileCubit(FakeProfileRepo());  addTearDown(cubit.close);   final states = expectLater(    cubit.stream,…
  14. 14

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

    Never sleep in a test.

    test('debounce runs only the last call after 300 ms of quiet', () {  fakeAsync((async) {    final calls = <String>[];    final debouncer = Debouncer(const Duration(milliseconds: 300));     debouncer.run(() => calls.add('f'));…
  15. 15

    How do you test code that makes HTTP calls without hitting the real network?

    Fake your repository for widget and bloc tests, and swap the HTTP client itself only when the code under test is the HTTP layer.

    import 'package:http/http.dart' as http;import 'package:http/testing.dart'; test('UserApi parses the user and sends the token', () async {  late http.Request sent;  final client = MockClient((request) async {…
  16. 16

    How do you widget-test a screen whose state lives in Riverpod or BLoC?

    Replace what the state layer depends on, not the widgets: override providers in a ProviderScope, or hand the screen a mocked bloc through BlocProvider.value, then assert on what is rendered.

    testWidgets('Riverpod: renders the profile from a fake repo', (tester) async {  await tester.pumpWidget(ProviderScope(    overrides: [profileRepoProvider.overrideWithValue(FakeProfileRepo())],    child: const MaterialApp(home: ProfilePage()),  ));  await tester.pump(); // let the FutureProvider resolve…
  17. 17

    How do you write golden tests in Flutter, and how do you keep them stable and out of the fast test loop?

    A golden test renders a widget, compares the pixels with a committed PNG through matchesGoldenFile, and fails on any difference; flutter test --update-goldens regenerates the image after an intended change.

    @Tags(['golden'])library; import 'package:flutter/material.dart';import 'package:flutter_test/flutter_test.dart';…
  18. 18

    How do you write integration tests with integration_test, and when do you need patrol?

    integration_test runs ordinary testWidgets code against the real app on an emulator or device, with real plugins and the real engine; patrol builds on it to also drive native UI such as permission dialogs and notifications.

    // integration_test/checkout_test.dartimport 'package:flutter/material.dart';import 'package:flutter_test/flutter_test.dart';import 'package:integration_test/integration_test.dart';import 'package:shop/main_test.dart' as app; // entry point with a fake backend…
  19. 19

    How do you measure code coverage in a Flutter project, and how should CI use it?

    flutter test --coverage writes coverage/lcov.info; strip generated files from it, then gate in CI on coverage of the changed lines rather than on a fixed total percentage.

    flutter test --coverage # drop generated code before measuringlcov --remove coverage/lcov.info \  '*.g.dart' '*.freezed.dart' '*/l10n/*' \  -o coverage/lcov.info…
  20. 20

    You refactored a repository's internals without changing behaviour and thirty tests went red: what were those tests doing wrong?

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

    // wrong: pins the call and the tree shape, breaks on any refactortestWidgets('loads the cart', (tester) async {  final repo = MockCartRepo();  when(() => repo.fetchPage(1)).thenAnswer((_) async => [apple]);  await tester.pumpWidget(MaterialApp(home: CartPage(repo: repo)));  await tester.pumpAndSettle();…
  21. 21

    Do you practise TDD, and how does writing tests first change the way you design classes?

    Red, green, refactor: write a failing test for the next behaviour, make it pass with the simplest code, then clean up.

  22. 22

    Your widget throws inside build on bad data, and the test dies with that exception: how do you assert on it instead?

    Claim it with tester.takeException().

    testWidgets('a malformed price surfaces as a FormatException', (tester) async {  await tester.pumpWidget(const MaterialApp(home: PriceTag(raw: 'n/a')));   // the framework already caught it; claim it or the test fails at the end  expect(tester.takeException(), isA<FormatException>());});…
  23. 23

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

    Suspect leaked shared state before timing: tests inside one file share globals, so a test that passes alone and fails after another one is reading what the earlier test left behind.

  24. 24

    Find the bug: this fakeAsync test stays green even when the expiry code is broken.

    test('session expires after 15 minutes of inactivity', () {  fakeAsync((async) async {    final session = Session(FakeAuthApi(), timeout: const Duration(minutes: 15));    await session.start(); // signs in, then starts the inactivity timer     async.elapse(const Duration(minutes: 15));    expect(session.isExpired, isTrue);  });});

    The fakeAsync callback is async, so it stops at await session.start() and hands back a future nobody waits for.

  25. 25

    How would you design the testing strategy for a large Flutter app: what goes into unit, widget, golden and integration tests?

    Push most checks down to fast unit tests of the domain and state layers, cover every screen's states with widget tests against fakes, guard the design system with goldens, and keep a small set of integration tests for the journeys that make money.