Irbisa · Cheatsheet
Testing
Unit tests, widget tests, integration tests, mocking
What are the three types of tests in Flutter, and when do you use each?
How do you structure a unit test in Dart:
test,group,setUp/tearDownandexpect?import 'package:flutter_test/flutter_test.dart'; void main() { group('Cart', () { late Cart cart;…How do you write a widget test:
pumpWidget, finders,tester.tapand matchers?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);…What is the difference between a mock, a stub and a fake, and when do you hand-write a fake instead of using mocktail?
class MockAnalytics extends Mock implements Analytics {} class InMemoryCartRepo implements CartRepo { final _items = <Item>[]; @override…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);});Find the bug: this mocktail test fails with
No matching calls, althoughbuyNowreally sendsOrder('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);});How do you test a widget that depends on
Theme,MediaQueryorLocalizations?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,…What is the difference between
pump,pumpAndSettle,pumpFramesandrunAsyncin a widget test?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() =>…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);});A widget test fails with
Found 2 widgets with text "Save": how do you narrow the finder without reaching for.first?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'…A widget test taps the 40th row of a
ListViewand fails with "nothing found", but the screen works fine in the app: what is happening?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'));…In a widget test, how do you prove that tapping a button navigated to the right screen?
testWidgets('cart button opens checkout', (tester) async { await tester.pumpWidget(MaterialApp( home: const CartPage(), routes: {'/checkout': (_) => const CheckoutPage()}, ));…How do you test async code and streams:
await,expectLaterandemitsInOrder?test('emits loading, then the profile', () async { final cubit = ProfileCubit(FakeProfileRepo()); addTearDown(cubit.close); final states = expectLater( cubit.stream,…How do you test a debounce, a retry with backoff, or a poll without making the suite slow?
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'));…How do you test code that makes HTTP calls without hitting the real network?
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 {…How do you widget-test a screen whose state lives in Riverpod or BLoC?
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…How do you write golden tests in Flutter, and how do you keep them stable and out of the fast test loop?
@Tags(['golden'])library; import 'package:flutter/material.dart';import 'package:flutter_test/flutter_test.dart';…How do you write integration tests with
integration_test, and when do you need patrol?// 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…How do you measure code coverage in a Flutter project, and how should CI use it?
flutter test --coverage # drop generated code before measuringlcov --remove coverage/lcov.info \ '*.g.dart' '*.freezed.dart' '*/l10n/*' \ -o coverage/lcov.info…You refactored a repository's internals without changing behaviour and thirty tests went red: what were those tests doing wrong?
// 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();…Do you practise TDD, and how does writing tests first change the way you design classes?
Your widget throws inside
buildon bad data, and the test dies with that exception: how do you assert on it instead?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>());});…The suite is green on your machine and fails one random test per CI run: how do you track that down?
Find the bug: this
fakeAsynctest 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); });});How would you design the testing strategy for a large Flutter app: what goes into unit, widget, golden and integration tests?