Reme Le Hane

Reme Le Hane

Unit Testing a Utility function that returns a Widget

Unit Testing a Utility function that returns a Widget

Subscribe to my newsletter and never miss my upcoming articles

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:

Widget testing passed in function In this post, we going to go through how, at least in my opinion, one would go about testing that a function is called…

Widget testing when your app needs access to directories. Many times in an application you would have a need to store files, temporarily or even permanently. For this, you are…

 
Share this