Irbisa · Шпаргалка
Тестирование
Юнит-тесты, тесты виджетов, интеграционные тесты, мокирование
Какие три вида тестов есть во Flutter и когда применять каждый?
Как устроен unit-тест в Dart:
test,group,setUp/tearDownиexpect?import 'package:flutter_test/flutter_test.dart'; void main() { group('Cart', () { late Cart cart;…Как написать widget-тест:
pumpWidget, finder-ы,tester.tapи матчеры?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);…Чем отличаются mock, stub и fake, и когда стоит написать fake руками вместо mocktail?
class MockAnalytics extends Mock implements Analytics {} class InMemoryCartRepo implements CartRepo { final _items = <Item>[]; @override…Этот тест на mocktail падает с
type 'Null' is not a subtype of type 'Future<void>': почему и как исправить?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);});Найдите баг: этот тест на mocktail падает с
No matching calls, хотяbuyNowдействительно отправляет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);});Как тестировать виджет, который зависит от
Theme,MediaQueryилиLocalizations?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,…Чем отличаются
pump,pumpAndSettle,pumpFramesиrunAsyncв widget-тесте?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() =>…Этот widget-тест падает с
pumpAndSettle timed out: что происходит и как исправить?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);});Widget-тест падает с
Found 2 widgets with text "Save": как сузить finder, не хватаясь за.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'…Widget-тест тапает по 40-й строке
ListViewи падает с «nothing found», хотя в приложении экран работает: что происходит?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'));…Как в widget-тесте доказать, что тап по кнопке открыл нужный экран?
testWidgets('cart button opens checkout', (tester) async { await tester.pumpWidget(MaterialApp( home: const CartPage(), routes: {'/checkout': (_) => const CheckoutPage()}, ));…Как тестировать асинхронный код и стримы:
await,expectLaterиemitsInOrder?test('emits loading, then the profile', () async { final cubit = ProfileCubit(FakeProfileRepo()); addTearDown(cubit.close); final states = expectLater( cubit.stream,…Как протестировать debounce, retry с backoff или поллинг, не замедляя тесты?
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'));…Как тестировать код с HTTP-запросами, не обращаясь к реальной сети?
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 {…Как написать widget-тест для экрана, состояние которого живёт в Riverpod или 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…Как писать golden-тесты во Flutter и как сделать их стабильными и убрать из быстрого прогона?
@Tags(['golden'])library; import 'package:flutter/material.dart';import 'package:flutter_test/flutter_test.dart';…Как писать интеграционные тесты на
integration_testи когда нужен 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…Как измерить покрытие кода во Flutter-проекте и как его стоит использовать в CI?
flutter test --coverage # drop generated code before measuringlcov --remove coverage/lcov.info \ '*.g.dart' '*.freezed.dart' '*/l10n/*' \ -o coverage/lcov.info…Вы отрефакторили внутренности репозитория, не меняя поведения, и покраснели тридцать тестов: что эти тесты делали не так?
// 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();…Практикуете ли вы TDD и как написание тестов первыми меняет то, как вы проектируете классы?
Ваш виджет бросает исключение в
buildна плохих данных, и тест умирает с этим исключением: как вместо этого проверить его?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>());});…Набор тестов зелёный на вашей машине, а в CI каждый прогон падает один случайный тест: как найти причину?
Найдите баг: этот тест с
fakeAsyncостаётся зелёным, даже если код истечения сессии сломан.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); });});Как бы вы спроектировали стратегию тестирования большого Flutter-приложения: что идёт в unit-, widget-, golden- и интеграционные тесты?