Probably not too common a use case, but for me I have a utility function for handling in-app navigation from the in-app web view webview_flutter
while it does have a generic error handler, I need to be handle errors differently based on what type of URL was selected as custom logic is required to be able to handle links that are not actually valid.
A snippet from the utility mentioned above:
dynamic launchURL(BuildContext context, String url) async {
if (url == 'about:blank') return;
if (url.startsWith('internal://')) {
final isGroups = url.contains('/groups');
if (isGroups) return _handleUnsupported(context);
}
}
void _handleUnsupported(BuildContext context) {
SnackBarUtil.error(
context: context,
message: LocalePathUtils.errorsLocalePath('unknownRoute'),
);
}
In this case, groups is a URL path that we currently do not support within the flutter app, so there is other logic specifically for groups, but for brevitiy we will reference the generic _handleUnsupported
function which returns a Snacbar with a localized error message.
Normally with a utility test, one would simply use expect
, however in this case the functions result would be a visual element, so before we can expect anything, we need to setup a test to allow all the logic to run.
testWidgets('cant', (tester) async {
when(mockUrlLauncherHelper.cl(any)).thenAnswer((_) async => false);
when(mockCoreCubit.state).thenReturn(fixtureCoreCubitLoaded);
// assert
const mockUrl = 'https://test.test';
Future<void> future(BuildContext context) async {
await Future.delayed(const Duration(seconds: 1));
return htmlContentUtils.launchURL(context, mockUrl);
}
final widget = providedLocalizedWidgetForTesting(
child: LayoutBuilder(
builder: (context, _) {
return FutureBuilder(
future: future(context),
builder: (ctx, snapshot) {
return const SizedBox.shrink();
},
);
},
),
);
await tester.pumpWidget(widget);
await tester.pumpAndSettle();
await tester.pump(const Duration(seconds: 1));
expect(find.byType(Flushbar), findsOneWidget);
});
There are actually quite a few things happening here in this test, but the most important part is the setup on line 13. Here we are setting up a test implementation of the widget, the provideLocalizedWidgetForTesting
is a test utility I setup, you can read about it HERE.
As the resulting Snackbar
also makes use of context for state access as well as localization I have included the LayoutBuilder
to get hold of the context from the test utility.
The utility function itself is a Future
and one of the easiest way to run those inside a widget is with the FutureBuilder
, which links back up to the future
function on line 8. Here is simply included a short delay to help manage ui rendering within the test context, otherwise you end up with it failing as needsBuild
gets called.
So once we have settled
and pumped
that additional second, we are now able to expect the Widget
that should be rendered on the screen.
As mentioned above, not a very common use-case, and when possible one should always keep UI and Utilities separate, and after having written this I had found a better way to deal with it in my use case, a way that did not involve returning a widget from a utility.
I felt however the learning itself was still valuable, and the possibility does exist that this is an unavoidable scenario for someone or even myself at a later stage. #SharingIsCaring
I hope you found this interesting, and if you have any questions, comments, or improvements, feel free to drop a comment. Enjoy your Flutter development journey :D
If you liked it, a like would be awesome, and if you really liked it, a cup of coffee would be great.
Thanks for reading.
If you wish to carry on with the subject of testing, why not take a look at: