FlutterのWidgetTestでImageのテスト

下記環境でFlutterアプリを開発中です。

|ツールなど|バージョンなど|
|:--|:--|
| MacBook Air Early2015|macOS Mojave 10.14.5|
|Android Studio |3.6.1|
|Java |1.8.0_131|
|Flutter|1.12.13+hotfix.5|
|Dart |2.7.0|

Imageのテストをしていて少しググったので、まとめておきます。
なお、Iconは`find.byIcon()`で簡単にできるのでここでは割愛します。

# AssetImage

`byWidgetPredicate`で、`Image`であり、かつその中に持つ`image`メンバーが`AssetImage`であり、かつその`image.keyName`が指定した`asstes`のパスと一致するか調べればテストできます。

サンプルコードは以下の通りです。
長いので、使い回しに便利になるよう`findByAssetImage`関数に出してあります。

```dart
void main(){
  /// 指定のpathを表示しているAssetImageを見つける
  Finder findByAssetImage(String path) {
    final finder = find.byWidgetPredicate((Widget widget) {
      if (widget is Image && widget.image is AssetImage) {
        final assetImage = widget.image as AssetImage;
        return assetImage.keyName == path;
      }
      return false;
    });
    return finder;
  }

  group('Imageのテスト', () {
    testWidgets('AssetImage', (WidgetTester tester) async {
      await tester
          .pumpWidget(MaterialApp(
          home: MyHomePage(),
        ),
      );

      expect(findByAssetImage('assets/home_bg.png'), findsOneWidget);
    });
  });
}
```

# NetworkImage

AssetImageのテストをまねて、以下のコードを書いても、失敗します。

```dart
void main(){
    /// 指定のurlを表示しているNetworkImageを見つける
    Finder findByNetworkImage(String path) {
      final finder = find.byWidgetPredicate((Widget widget) {
        if (widget is Image && widget.image is NetworkImage) {
          final networkImage = widget.image as NetworkImage;
          return networkImage.url == path;
        }
        return false;
      });
      return finder;
    }

    group('Imageのテスト', () {
      testWidgets('Network Image', (WidgetTester tester) async {
          String path = 'https://hogehoge.com/sample.png';
          await tester.pumpWidget(
            MaterialApp(
              home: Image.network(path),
            ),
          );
          expect(findByNetworkImage(path), findsOneWidget);
      });
    });
  });

}
```

`Image.network()`で読み込んだ`Image`があると、実はウィジェットテストは失敗してしまいます。
必ず404が返ってきてしまうのです。
これはTesterの中でHttpClientがモック化されて何も通信しないようになっていて、そのせいのようです。

参考:
https://github.com/flutter/flutter/issues/21870
https://stackoverflow.com/questions/49166234/flutter-widget-tests-with-networkimage

で、これを解決するために、便利なライブラリを提供して下さっている方がいます。
(上記参考ページのStackoverflowで作者が回答していますw)

https://pub.dev/packages/image_test_utils

使い方は**Installing**タブにあるとおりですが、まず、`pubspec.yaml`に以下の記述を追加します。

```pubspec.yaml
dev_dependencies:
  # network image test
  image_test_utils: ^1.0.0
```

`dev_dependencies:`下に追加するのを間違えないで下さい。

次に、`testWidget`内のテストコードの記述を、`provideMockedNetworkImages`で括ります。

```dart
void main(){
    /// 指定のurlを表示しているNetworkImageを見つける
    Finder findByNetworkImage(String path) {
...
    }

    group('Imageのテスト', () {
      testWidgets('Network Image', (WidgetTester tester) async {
        provideMockedNetworkImages(() async { // 追加
          String path = 'https://hogehoge.com/sample.png';
          await tester.pumpWidget(
            MaterialApp(
              home: Image.network(path),
            ),
          );

          expect(findByNetworkImage(path), findsOneWidget);
        }); // 追加
      });
    });
  });
}
```

これで画像自体はダミーを読み込みつつ、セットされたurlが意図通りかというテストは出来るようになります。

また、Imageのテストはしないけどページ内にHttpアクセスがあり自動的にキックされてしまうような実装の場合にも有用ですね。

# 参考サイト
[Flutter] Widget Test の逆引き Tips
https://qiita.com/sensuikan1973/items/161c8efa1a47d1ceb563