Kinokのブログ

しゃかいじん。

DIしてflutterアプリのテスタビリティを上げる

概要

今更感あるので需要もそんなにないだろうけども、ブログを書くペースを落としたくないので書いてみます。

DI(Dependency Injection)

依存性の注入です。

なんか内部で依存しているやつを外部から注入できるようにするやつです。

依存性の逆転の法則とはまた別なので注意が必要です。

なぜ必要か

  • 内部でオブジェクトを生成するとテストしづらいからです。
  • 詳細に依存すると結合度が高くなっちゃうから

どうやるか

色々やり方はあります。

  • interfaceを定義して引数に渡す
  • 必要な関数を引数に渡す
  • setterでオブジェクトを注入する

flutterでのケース

よくある(自分が直面した)のはwidgetテストやunitテストの際にモックできないなどでしょう。

例えば、「対象の記事をいいねするボタンをテストする」ケースを想像しましょう。

  group('KeepButton', () {
    testWidgets('will activate [onPressed] call', (WidgetTester tester) async {
      await tester.pumpWidget(
        MaterialApp(
          home: LikeButton(
            onPressed: (_) {
              expect('called', 'called');
            },
          ),
        ),
      );
      await tester.pumpAndSettle();
      await tester.tap(find.byType(KeepButton));
    });

うまくいきそうでしょうか? 実はテストは落ちてしまいます。

なぜなら内部でLikeButtonがいいねされているかどうかを確認するために、providerを利用していたからです。(Riverpodなら問題ないかもしれないですが、providerだとcontextに載せたインスタンスもmockしなくちゃいけない)

class KeepButton extends StatelessWidget {
  final void Function(Target target) onPressed;

  @override
  Widget build(BuildContext context) {
    return Selector<AppStore, bool>(selector: (_, store) {
      // 省略
  }
}

こういった場合は必要なbool値だけフィールドとして設定し、親Widgetからselectorで取得することで「依存性を注入」することができます。

補足/まとめ

riverpodだとモックとかも簡単にできてなんか便利そうですね。

riverpod.dev