🏆

【Flutter】Sizer対応したGoldenTestを実装する

2021/10/30に公開約6,500字

はじめに

Flutterでアプリを開発する時のResposive対応でSizerライブラリを使う方多いんじゃないでしょうか。

今回はそのSizerライブラリに対応したGoldenTestを実装していきます。
実装そのものは簡単ですが、詰まったところがあったので書き留めておきます。

GoldenTestなにそれ?

って方は以下記事を参考にしてください。

https://zenn.dev/matsumaru/articles/c2bf8ec468cff8
https://zenn.dev/matsumaru/articles/44b4dc9927bb41
https://zenn.dev/matsumaru/articles/0971ee5af5fdc9

今回のゴール

GoldenTestのマスタースクリーンショットで、実機と同じレイアウトにすることを目標とします。以下画像のようになればOKとする。
FirstPageはIconとTextだけのシンプルなWidgetでSizerを使っているのでデバイスサイズに応じて変化します。FirstPageの中身は最後の方にのせておきます。

実機の画面

FirstPage(小) FirstPage(大)

GoldenTestのスクリーンショット

FirstPage(小) FirstPage(大)

Sizer対応

ただ単にtestでSizerを入れるだけです。

void main() {
  testGoldens('golden test', (WidgetTester tester) async {
    await loadAppFonts();

    //テストしたいWidgetをSizerで包んであげる。
    final widget = Sizer(builder: (context, orientation, deviceType) {
        //任意のWidget
      return FirstPage();
    });

    await tester.pumpWidgetBuilder(widget);
    await multiScreenGolden(tester, 'golden_test');
  });
}

もし、Sizerで包んでいない場合は以下のようなエラーが出てくると思います。

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

The relevant error-causing widget was:
  FirstPage

When the exception was thrown, this was the stack:
#0      SizerUtil.width (package:sizer/util.dart)
#1      SizerExt.w (package:sizer/extension.dart:12:36)
#2      FirstPage.build (package:sizer_golden_test_demo/main.dart:35:25)
#3      StatelessElement.build (package:flutter/src/widgets/framework.dart:4739:28)
#4      ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4665:15)
#5      Element.rebuild (package:flutter/src/widgets/framework.dart:4355:5)
#6      ComponentElement._firstBuild (package:flutter/src/widgets/framework.dart:4643:5)
#7      ComponentElement.mount (package:flutter/src/widgets/framework.dart:4638:5)
...     Normal element mounting (211 frames)
#218    Element.inflateWidget (package:flutter/src/widgets/framework.dart:3673:14)
#219    MultiChildRenderObjectElement.inflateWidget (package:flutter/src/widgets/framework.dart:6333:36)
#220    MultiChildRenderObjectElement.mount (package:flutter/src/widgets/framework.dart:6344:32)
...     Normal element mounting (362 frames)
#582    Element.inflateWidget (package:flutter/src/widgets/framework.dart:3673:14)
#583    Element.updateChild (package:flutter/src/widgets/framework.dart:3422:20)
#584    RenderObjectToWidgetElement._rebuild (package:flutter/src/widgets/binding.dart:1198:16)
#585    RenderObjectToWidgetElement.update (package:flutter/src/widgets/binding.dart:1175:5)
#586    RenderObjectToWidgetElement.performRebuild (package:flutter/src/widgets/binding.dart:1189:7)
#587    Element.rebuild (package:flutter/src/widgets/framework.dart:4355:5)
#588    BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2620:33)
#589    AutomatedTestWidgetsFlutterBinding.drawFrame (package:flutter_test/src/binding.dart:1139:19)
#590    RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:319:5)
#591    SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1143:15)
#592    SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1080:9)
#593    AutomatedTestWidgetsFlutterBinding.pump.<anonymous closure> (package:flutter_test/src/binding.dart:1006:9)
#596    TestAsyncUtils.guard (package:flutter_test/src/test_async_utils.dart:71:41)
#597    AutomatedTestWidgetsFlutterBinding.pump (package:flutter_test/src/binding.dart:993:27)
#598    WidgetTester.pumpWidget.<anonymous closure> (package:flutter_test/src/widget_tester.dart:554:22)
#601    TestAsyncUtils.guard (package:flutter_test/src/test_async_utils.dart:71:41)
#602    WidgetTester.pumpWidget (package:flutter_test/src/widget_tester.dart:551:27)
#603    _pumpAppWidget (package:golden_toolkit/src/testing_tools.dart:128:16)

詰まったところ

ちゃんとSizerで包んでやったのでもう大丈夫だと思っていたら、なんだかレイアウトが実機と全然違う現象がおきました。

その原因はconstでした!
テストしたいWidgetにconstがついている場合、multiScreenGoldenを実行しても、デバイスサイズに応じてレイアウトが変わってくれません。

模擬的にtest内でWidgetにconstをつけてflutter test --update-goldensしてみると、、

void main() {
  testGoldens('golden test', (WidgetTester tester) async {
    await loadAppFonts();

    final widget = Sizer(builder: (context, orientation, deviceType) {
        //任意のWidgetにconstがついているとする
      return const FirstPage();
    });

    await tester.pumpWidgetBuilder(widget);
    await multiScreenGolden(tester, 'golden_test');
  });
}

GoldenTestのスクリーンショット(const有りパターン)

Head Head

見ての通りIconもTextもサイズが変わっておらず、Sizer対応していないです。

FirstPage

今回テストしているWidgetです。

import 'package:flutter/material.dart';
import 'package:sizer/sizer.dart';

void main() {
  runApp(const MyApp());
}

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

  
  Widget build(BuildContext context) {
    return Sizer(builder: (context, orientation, deviceType) {
      return MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: FirstPage(),
      );
    });
  }
}

class FirstPage extends StatelessWidget {
  const FirstPage({Key? key}) : super(key: key);
  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Container(
              width: 20.w,
              height: 30.h,
              child: Icon(
                Icons.flutter_dash_rounded,
                size: 40.sp,
              ),
            ),
            Text('Hello world', style: TextStyle(fontSize: 12.sp)),
          ],
        ),
      ),
    );
  }
}

終わりに

参考になれば幸いです。

Discussion

ログインするとコメントできます