Unit test and widget test in flutter
- 3 minutes read - 442 wordsIt is quite easy to refactor code using IDEs, however it will be a different story considing to refactor to testable code.
Concerns
The refactor functionality in IDE is based on rigor theories and algorithm to make sure the refactors don’t change the behaviours of programs and get it better organized. One of the theories, can be found here
Target
Refactor code to testable codes following best practices. Reading materialis about those best practices is as following:
Typical code
import ''./utils/test_app.dart'; //TestApp with callback
// util.dart
// click(tester, targetKey) async;
// bindMethodChannel(tester)
import './utils/util.dart';
test("test ", () async {
bindMethodChannel(tester);
...
debugDefaultTargetPlatformOverride = null;
});
run all tests
flutter test
Issue
unit test and widget test
-
parameterized tests
group("formatDay should format dates correctly:", () { var inputsToExpected = { DateTime(2018, 11, 01): "Thu 1", ... DateTime(2018, 11, 07): "Wed 7", DateTime(2018, 11, 30): "Fri 30", }; inputsToExpected.forEach((input, expected) { test("$input -> $expected", () { expect(formatDay(input), expected); }); }); });
-
flutter plugins don’t support unit tests and widget tests: put in integration_test folder
flutter test integration_test/upgrade_controller_test.dart --flavor official
-
GetPlatform is not compatible with unit tests
// use defaultTargetPlatform defaultTargetPlatform == TargetPlatform.iOS
-
some plugins don’t provide methodchannels for all platforms.
// import corresponding interface and method_channel,mock them import 'package:path_provider_platform_interface/path_provider_platform_interface.dart'; import 'package:path_provider_platform_interface/src/method_channel_path_provider.dart'; final provider = MethodChannelPathProvider(); PathProviderPlatform.instance = provider; tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( provider.methodChannel, (MethodCall methodCall) { expect(methodCall.method, 'getApplicationSupportDirectory'); return Future.value(''); }, );
-
specify target platform: 1
debugDefaultTargetPlatformOverride = TargetPlatform.android; // at the end of testWidgets, add // debugDefaultTargetPlatformOverride = null;
-
specify target platform: 2
const platformVariants = TargetPlatformVariant( <TargetPlatform>{TargetPlatform.iOS, TargetPlatform.android}); testWidgets( "test", (WidgetTester tester) async { // }, variant: platformVariants, );
-
global functions, global variables, or class static members
// convert global function and global variables to a utility class // or miscellaneous class and make it singleton. // class static members: singleton // inject singleton
-
infinite loop:
extract the inner block of the loop to another function, call the new function in the loop, test the new function
-
Test breaks when using Future.delayed
//use tester.runAsync await tester.runAsync(() async { await upgC.watchAppVersionImpl(); });
-
global functions, variables of Getx , BotToast:
//wrap the call in widget event calls final Key targetKey = new UniqueKey(); await tester.pumpWidget( GetMaterialApp( localizationsDelegates: const [ GlobalWidgetsLocalizations.delegate, GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, S.delegate, ], supportedLocales: S.delegate.supportedLocales, home: Scaffold( key: globalKey, floatingActionButton: FloatingActionButton( key: targetKey, onPressed: () { upgC.doUpdate(true); }), bottomNavigationBar: const BottomAppBar(), ), ), ); await tester.pumpAndSettle(); await click(tester, targetKey); // verify using expect function
Integration Testing
TODO
E2E Testing
TODO