🛠️

【Flutter】l10nを含んだメソッドをテストする

2024/05/04に公開

はじめに

以下のようなl10n(localization)が絡んだメソッドのユニットテストを書こうとした時に、contextをどう用意するかで少し悩んだので、私なりの解決方法を書こうと思います。

class TextGenerator {
  static String getHogeText(BuildContext context) {
    return AppLocalizations.of(context)!.hoge;
  }
}

実装

結論から話すと以下のように実装しました。
l10nの設定を渡したMaterialApp()内のBuildContextを取得して使っています。

testWidgets("description", (tester) async {
  await tester.pumpWidget(const MaterialApp(
    localizationsDelegates: AppLocalizations.localizationsDelegates,
    supportedLocales: AppLocalizations.supportedLocales,
    home: Placeholder(),
  ));

  await tester.pumpAndSettle();

  final BuildContext context = tester.element(find.byType(Placeholder));

  expect(
    TextGenerator.getHogeText(context),
    AppLocalizations.of(context)!.hoge,
  );
});

解説

なぜ上記のように実装したかを説明します。

await tester.pumpWidget(const MaterialApp(
  localizationsDelegates: AppLocalizations.localizationsDelegates,
  supportedLocales: AppLocalizations.supportedLocales,
  home: Placeholder(),
));

まず、実際にアプリ内で使っているl10nの設定を渡したMaterialApp()に、適当なWidgetを渡しています。(Placeholder()の部分はContainer()でもなんでもいいです。)
ここで、localizationsDelegates:supportedLocales:を設定してあげないと、AppLocalizations.of(context)nullになります。
(設定という表現が適切かは分かりませんが、この記事ではそう呼びます。)

なぜnullになるのか?

AppLocalizations.of()of()の中を辿っていくと、以下のresourcesFor()が呼ばれています。
https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/widgets/localizations.dart#L616-L619
_typeToResourcesには今のcontextに含まれているl10nの設定が格納されており、その中にTが存在するかを確認しています。
AppLocalizations.of()を呼んだ際、TにはAppLocalizations?が入るので、要するに今のcontextAppLocalizationsが設定されているかをチェックします。されていなければnullが返ります。


await tester.pumpAndSettle();

次に、描画が終わるまで待ちます。
これが無いと、描画が終わる前にBuildContextを取得しようとして失敗し、テストが落ちることがあります。
(l10nの量によっては無くても動きますが、あった方が安心です。)

final BuildContext context = tester.element(find.byType(Placeholder));

次に、使用したいl10nの設定が存在するwidget tree内からBuildContextを取得します。
find.byType()の引数には、MaterialApp()homeに渡したWidgetの型を指定します。

elementって?

ここで呼んでいるtester.element()が返すElementですが、
https://github.com/flutter/flutter/blob/master/packages/flutter_test/lib/src/controller.dart#L859-L862

Element TreeのElementです。
https://docs.flutter.dev/resources/architectural-overview#layout-and-rendering

ElementBuildContextを実装(implements)しているので、BuildContextとして受け取れます。
https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/widgets/framework.dart#L3378

普段使っているWidgetのbuild()メソッドには、
StatelessElementや、
https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/widgets/framework.dart#L5546

StatefulElement等が渡されています。
https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/widgets/framework.dart#L5562

例えば、StatelessWidgetbuild()メソッドではStatelessElementが渡されます。
buildメソッドに、が渡されている様子


expect(
  TextGenerator.getHogeText(context),
  AppLocalizations.of(context)!.hoge,
);

これでAppLocalizationsが使えるcontextが取得出来たので、無事にテスト出来ます。

終わりに

ユニットテストでtestWidgets()を使っていることに気持ち悪さはありますが、contextに依存する以上、こうするしか方法がないのかなぁと思います。

参考

Discussion