FutureBuilderのView側のテスト方法について。
ついでにgolden_toolkitでCircularProgressIndicator(のようなエンドレスアニメーション)のスナップショットの撮りかた。
前提条件
MVVMパターンなどで、FutureをDIで渡せるような作りでのView側のテストについて。
テスト対象のウィジェット
単純化するためFutureメソッドは上位から与えるようにしているけど、DIで渡されたクラスメンバーを使う場合は、providerであればbuild内でcontext.watch()などで取り出したオブジェクトメンバーを渡すようになる。
そして渡されたFutureは何らかのある程度時間がかかる処理を行うというものだろう。
class MyWidget extends StatelessWidget {
const MyWidget(this.future, {super.key});
final Future<String> future;
@override
Widget build(BuildContext context) {
return FutureBuilder<String>(
future: future,
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return const Text("none");
case ConnectionState.waiting:
return const CircularProgressIndicator();
case ConnectionState.active:
return const Text("active");
case ConnectionState.done:
return Text(snapshot.data ?? "null");
}
});
}
}
テスト側
テストでは渡すFutureのモックを作るのだけど、そのモックにはCompleterを使うことで、FutureBuilderのAsyncSnapshot.connectionStateの状態を制御するというものになる。
void main() {
testWidgets("test", (tester) async {
final mock = Completer<String>();
final target = MaterialApp(home: MyWidget(mock.future));
await tester.pumpWidget(target);
expect(find.byType(CircularProgressIndicator), findsOneWidget);
mock.complete("hoge");// doneに制御を移す
await tester.pump();
expect(find.byType(CircularProgressIndicator), findsNothing);
expect(find.text("hoge"), findsOneWidget);
});
上のmock.completeを呼び出すことでFutureが完了状態になり、FutureBuilderのAsyncSnapshot.connectionStateがdoneになる。
前は、上の方法を知らなかったのでdelayなど噛ませて、時間調整等をしてたんだけどそれが必要なくなった。
golden_toolkitでCircularProgressIndicatorのスナップショットの撮りかた
この件、screenMatchesGoldenでスナップショットを撮ろうと四苦八苦してしまった。
どうしても、タイムアウトの例外でスナップショットが撮れない。
調べてみたところCircularProgressIndicatorの様にエンドレスなアニメーションの場合、そのアニメーションが止まってテスト側に処理を渡すタイミングがないので、タイムアウトになってしまうのだと。
以下の様な実装をすることでスナップショットを撮ることができた。
screenMatchesGoldenのcustomPumpにスナップショットを撮るタイミングを指定するようだ。
await screenMatchesGolden(
tester,
'waiting',
customPump: (tester) async =>
await tester.pump(const Duration(milliseconds: 500)),
);
コメント