🧪

「flutter_screenutil」利用時のWidget Test

2023/06/24に公開

はじめに

flutter_screenutil を利用しているアプリのWidget Testを書く時に躓いたポイントがあったので備忘録です。

今回使用した環境は以下になります。

Flutter 3.7.12
flutter_screenutil: 5.7.0

解説

今回は下記コードを元に解説していきます。fooというテキストが表示される単純な画面を持つアプリです。

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return ScreenUtilInit(
      designSize: const Size(360, 690),
      builder: (BuildContext context, Widget? child) {
        return MaterialApp(
          home: child,
        );
      },
      child: const SampleScreen(),
    );
  }
}

class SampleScreen extends StatelessWidget {
  const SampleScreen({Key? key}) : super(key: key);

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('title'),
      ),
      body: Center(
        child: Text('foo', style: TextStyle(fontSize: 20.sp)),
      ),
    );
  }
}

上記のSampleScreenをテストすることを考えます。fooという文字列をの持つwidgetが存在するかどうかをテストするコードを書くと以下のようになります。

void main() {
  testWidgets('test', (WidgetTester tester) async {
    await tester.pumpWidget(const SampleScreen());

    expect(find.text('foo'), findsOneWidget);
  });
}

しかし、これを実行すると下記のエラーが出ます。

══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
The following LateError was thrown building SampleScreen(dirty):
LateInitializationError: Field '_minTextAdapt@237084504' has not been initialized.

widget内で.spというscreenutilの機能を使っていますが、SampleScreen単体で切り出した時screenutilの初期化は反映されないのでエラーが出ている状態です。

そのため、下記のようにテストコード内にscreenutilの初期化を行うコードを追加してあげます。

void main() {
  testWidgets('test', (WidgetTester tester) async {
    await tester.pumpWidget(
+     ScreenUtilInit(
+       designSize: const Size(360, 690),
+       builder: (_, __) {
+         return const SampleScreen();
+       },
+     ),
    );

    expect(find.text('foo'), findsOneWidget);
  });
}

この状態で実行すると、次は以下のエラーが出ます。

══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
The following assertion was thrown building Scaffold(dirty, state: ScaffoldState#c2069(tickers:
tracking 2 tickers)):
No Directionality widget found.
Scaffold widgets require a Directionality widget ancestor.
The specific widget that could not find a Directionality ancestor was:
  Scaffold
The ownership chain for the affected widget is: "Scaffold ← SampleScreen ← ConstrainedBox ←
  Container ← FittedBox ← ConstrainedBox ← Container ← Builder ← MediaQuery ← ScreenUtilInit ← ⋯"
Typically, the Directionality widget is introduced by the MaterialApp or WidgetsApp widget at the
top of your application widget tree. It determines the ambient reading direction and is used, for
example, to determine how to lay out text, how to interpret "start" and "end" values, and to resolve
EdgeInsetsDirectional, AlignmentDirectional, and other *Directional objects.

ScaffoldはDirectionality Widgetが親に必要ですが、存在しないためエラーになっているようです。Directionality WidgetとしてはMaterialAppCupertinoAppなどが該当するのでこれらを使用します。 ※このエラーはflutter_screenutilとは関係ないです

今回は下記のようにMaterialAppで包んでやりました。

void main() {
  testWidgets('test', (WidgetTester tester) async {
    await tester.pumpWidget(
      ScreenUtilInit(
        designSize: const Size(360, 690),
        builder: (_, __) {
+         return const MaterialApp(
+           home: SampleScreen(),
+         );
        },
      ),
    );

    expect(find.text('foo'), findsOneWidget);
  });
}

これで無事テストが通るようになります 🎉

共通のラッパー

無事テストが通るようになりましたが、切り出したコンポーネントでテストを行う時に毎回テストコード内にscreenutilの初期化やMaterialAppで包むコードを書くのは冗長です。そのため下記のような共通のラッパーを用意しておくと便利です。

class WidgetTestWrapper extends StatelessWidget {
  const WidgetTestWrapper({Key? key, required this.child}) : super(key: key);

  final Widget child;

  
  Widget build(BuildContext context) {
    return ScreenUtilInit(
      designSize: const Size(360, 690),
      builder: (_, __) {
        return MaterialApp(
          home: child,
        );
      },
    );
  }
}

以下のようにして使います。だいぶスッキリしますね。

void main() {
  testWidgets('test', (WidgetTester tester) async {
    await tester.pumpWidget(
      const WidgetTestWrapper(child: SampleScreen()),
    );

    expect(find.text('foo'), findsOneWidget);
  });
}

以上です。

参考

https://github.com/OpenFlutter/flutter_screenutil/issues/115

https://coneta.jp/article/17aa4205-7ed3-4fdc-ae92-2905c4f85044/

Discussion