Unit Testing a Utility function that returns a Widget

Search for a command to run...

No comments yet. Be the first to comment.
Here we will cover various aspects of unit, widget and integration testing in Flutter
Using dependency injection can be great for simplifying your code and keeping it DRY, it can also simplify testing and even improve testability. If you have not started using dependency injection in your app, you may want to take a look at get_it. Fo...
The Problem Wasn’t Triage Most AI workflows in software engineering still keep the human directly in the middle of triage. The AI might help write code. It might explain a stack trace. It might summar

There’s been a steady undercurrent of people switching tools lately. Copilot to Claude. Claude to something else. Codex quietly entering more workflows. A lot of confidence in different directions, of

It’s been said to me that at times my responses can come across as a “no” first-unintentionally, of course, but real all the same. Not long ago, someone on my team shared an idea they were exploring. I began with a caution, meaning to help them avoid...

The opening sentence of any message often carries more weight than everything that follows. I’ve learned this the hard way. Recently, a report shared something he wanted to pursue. I was supportive — I really was — but my first response came as a cau...

One of the things I’ve always been comfortable with is admitting when I don’t know something. To me, it’s one of the real distinctions between the more and less experienced engineers — and honestly, people in general. When you’re earlier in your care...

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: