LoginSignup
16
5
お題は不問!Qiita Engineer Festa 2023で記事投稿!

【Flutter】screenutilに対応したGolden Testの実装

Last updated at Posted at 2023-07-20

はじめに

現在進めているプロジェクトでは画面サイズに合わせてWidgetやフォントサイズを調整できるよう
flutter_screenutilを導入しています。

Widget Test実装時はGolden Testで実装しているのですがその際に詰まったことを備忘録がてら書きます。

前提

flutter_screenutilとは?
Widgetやフォントサイズを動的に変更できる便利なパッケージです。
詳細な説明は省きますが公式を見て初期設定さえすれば簡単に実装することができます。

Golden Testについて
Golden Testの詳細は省きますが今回の対応ではgoolden_toolkitを利用しています。
Flutterではパッケージを導入せずともGolden Testの実装は可能ですが、よりテストを実装しやすくするために上記のパッケージを導入しています。

今回のソースコードはGithubにおいてあるので詳細気になる方は見てください。

目次

  1. 簡単なサンプルの確認
  2. 実際に発生したエラー
  3. 解消法
  4. 最後に
  5. 参考文献

簡単なサンプルの確認

Flutterのチュートリアルでも利用されているカウンターアプリにGolden Testを実装してみます。
flutter_test_configを以下のように実装します。flutter_test_configの詳細については以下を読めばわかりやすいと思います。
flutter_test library

テストコード実装の際にWidget Testの対象となるWidgetを指定すると思うのですがその時にpageWrapperでラップするとプロジェクト独自のtheme等の設定を適用できるので以下の様な構成にしています。

TestDeviceではgolden_tookitで異なるデバイスのUIを確認する際のデバイス一覧を定義しています。
Device.iphone11の様な書き方をすると簡単にDeviceを定義することもできます。

flutter_test_config.dart
Future<void> testExecutable(FutureOr<void> Function() testMain) async {
  return GoldenToolkit.runWithConfiguration(
    () async {
      await loadAppFonts();
      await testMain();
    },
    config: GoldenToolkitConfiguration(
      // Currently, goldens are not generated/validated in CI for this repo. We have settled on the goldens for this package
      // being captured/validated by developers running on MacOSX. We may revisit this in the future if there is a reason to invest
      // in more sophistication
      skipGoldenAssertion: () => !Platform.isMacOS,
      enableRealShadows: true,
    ),
  );
}

Widget pageWrapper(Widget widget) =>
  MaterialApp(
    // ThemeやfontoFamiliyの指定はココで
    home: widget,
    debugShowCheckedModeBanner: false,
  );

class TestDevice {
  static const all = [
    Device(
      size: Size(375, 667), 
      name: 'iPhoneSE',
      devicePixelRatio: 3,
    ),
    Device(
      size: Size(375, 812), 
      name: 'iPhone13Mini',
      devicePixelRatio: 3,
    ),
    Device(
      size: Size(428, 926), 
      name: 'iPhone13ProMax',
      devicePixelRatio: 3,
    ),
  ];
}

肝心のテストコードは以下です。

screenutil_golden_test.dart
void main() {
  testGoldens('MyHomePage', (tester) async {
    final builder = DeviceBuilder()
      ..overrideDevicesForAllScenarios(devices: TestDevice.all)
      ..addScenario(
        widget: materialWrapper(const MyHomePage(title:'screenutil_golden_demo')),
        name: 'default',
      );

    await tester.pumpDeviceBuilder(builder);
    await screenMatchesGolden(tester, 'MyHomePage');
  });
}

実際に発生したエラー

flutter test --update-goldensすると各デバイスサイズのスクリーンショットが生成されるイメージだったのですが以下のエラーが発生しました。

══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
The following LateError was thrown building MyHomePage(dirty, state: _MyHomePageState#9eb02):
LateInitializationError: Field '_minTextAdapt@249084504' has not been initialized.

The relevant error-causing widget was:
  MyHomePage

When the exception was thrown, this was the stack:
#0      ScreenUtil._minTextAdapt (package:flutter_screenutil/src/screen_util.dart)
#1      ScreenUtil.scaleText (package:flutter_screenutil/src/screen_util.dart:177:7)
#2      ScreenUtil.setSp (package:flutter_screenutil/src/screen_util.dart:204:44)
#3      SizeExtension.sp (package:flutter_screenutil/src/size_extension.dart:18:33)
#4      _MyHomePageState.build (package:screenutil_golden_demo/main.dart:53:65)
#5      StatefulElement.build (package:flutter/src/widgets/framework.dart:5080:27)
#6      ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4968:15)
#7      StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5133:11)
#8      Element.rebuild (package:flutter/src/widgets/framework.dart:4690:5)
#9      ComponentElement._firstBuild (package:flutter/src/widgets/framework.dart:4950:5)
#10     StatefulElement._firstBuild (package:flutter/src/widgets/framework.dart:5124:11)
#11     ComponentElement.mount (package:flutter/src/widgets/framework.dart:4944:5)
...     Normal element mounting (214 frames)

スタックトレースを確認するとscreenutilに起因するエラーっぽい。。
screenutilパッケージを利用する際の初期設定がテストコードでは反映されていないことが原因っぽいことはわかるような。。

以下のようにpageWrapperを書き換えて見ましたが今度はスクリーンショットの解像度?がおかしくなる。。

flutter_test_config.dart
Widget pageWrapper(Widget widget) =>
  ScreenUtilInit(
    designSize: const Size(375,812),
    builder: (context, child) =>
      MaterialApp(
        home: widget,
        debugShowCheckedModeBanner: false,
      )
  );

実際に生成されたスクリーンショットは以下
スクリーンショット 2023-06-11 22.56.14.png

解消方法

screenutilのリポジトリでそれっぽいissueがたてられていました。
MediaQueryでラップする必要があるっぽい。。?
とりあえず上記で指定されたやり方でpageWrapperを実装してみます。

flutter_test_config.dart
class _Wrapper extends StatelessWidget {
  const _Wrapper(this.child);
  final Widget child;
  @override
  Widget build(BuildContext context) {
    ScreenUtil.init(context, designSize: const Size(1400, 926),);
    return child;
  }
}

Widget pageWrapper(Widget widget) => 
  MediaQuery(
    data: const MediaQueryData(), 
    child: MaterialApp(
      home: Scaffold(body: _Wrapper(widget)),
      debugShowCheckedModeBanner: false,
    ),
  );

テスト実行してみるとうまくいっているようです。
スクリーンショット 2023-06-11 23.04.08.png

最後に

なぜテストコード側だけMediaQueryでラップする必要があるかは分からないですが公式のexampleを見た感じだと同様にテストコードを実装していたのでこのやり方で問題はなさそうです。
パッケージの実装を確認しましたが、レスポンシブ対応するためにMediaQueryを利用していました。
なので、テストコード側もMediaQueryでラップして画面サイズ等の情報を定義する必要があるといった理屈なんですかね。
もっとクリティカルな根拠を見つけたら追記します。

参考文献

16
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
5