Flutter Widget Test Wrapper

When testing a widget, it needs to run in the correct environment, otherwise unrelated errors may occur.

The Widget’s Test Environment

Different widgets have different dependencies, and expect different things in the widget tree.

  • Material widgets expect a MaterialApp or Scaffold or Material widget ancestor.

  • Widgets that navigate to others using GoRouter for example, expect MaterialApp.router with a routerConfig.

  • A widget that uses Riverpod needs a ProviderScope or Container ancestor.

  • An app in multiple languages probably needs a specific locale set.

  • Other packages may require initialisation, e.g. Log.

Supplying the Correct Environment

I use the following wrapper for my widgets under test:

/// Wrap `widget` with a Material app for localizing text, etc.
Widget buildForLocalizedTesting({
  required Widget widget,
  List<Override> overrides = const <Override>[],
  GoRouter? goRouter,
}) {
  Log.init();

  tz_db.initializeTimeZones();
  initializeDateFormatting();

  // Initialise Riverpod, using provided overrides
  return ProviderScope(
    overrides: overrides,
    // Provide a `Material` ancestor for material widgets
    child: MaterialApp.router(
      localizationsDelegates: const <LocalizationsDelegate<dynamic>>[
        AppLocalizations.delegate,
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      supportedLocales: const <Locale>[
        Locale('en', ''),
      ],
      // Use the provided goRouter, or a minimal default
      routerConfig: goRouter ??
          GoRouter(
            initialLocation: '/',
            routes: [
              GoRoute(
                name: 'root',
                path: '/',
                builder: (_, __) => Scaffold(body: widget),
              ),
            ],
          ),
    ),
  );
}

This is customised for one of my apps, and should be modified to suite the app you’re testing.

It does the following:

  • Wraps the widget in both MaterialApp and Scaffold, to ensure that material widgets have a material ancestor,
  • Provides a default GoRouter config for a single route to the widget under test, while also allowing the test to pass in any router config it needs,
  • Wraps the all the above in ProviderScope to initialise Riverpod, also allowing the test to pass in any provider overrides it needs,
  • Initialises the supported locales to be only English (this should probably be another parameter, defaulting to English), and
  • Initialises other packages used in the app (Log, timezonedatabase, etc).

Leave a comment