⏲️

RiverpodのFutureProviderの実装からテスト

2024/12/04に公開

FlutterでRiverpodのFutureProviderを使った実装からウィジェットテストまでの方法をまとめておきます。

前提

次のパッケージがインストールされていることとします。

pubspec.yaml
dependencies:
  riverpod: ^2.6.1
  riverpod_annotation: ^2.6.1
  hooks_riverpod: ^2.6.1
  
dev_dependencies:
  riverpod_generator: ^2.6.3

実装

今回は3秒後に文字列が表示されるウィジェットを考えます。

Future Provider

3秒後に文字列を返すFurureProviderを作ります。
riverpod_annotationを使うと、@riverpodというアノテーションを付けるだけで簡単にProviderを定義できます。

sample_provider.dart
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:riverpod/riverpod.dart';

part 'sample_provider.g.dart';


FutureOr<String> sampleState(Ref ref) async {
  await Future.delayed(const Duration(seconds: 3)); // 3秒待つ
  return 'Hello, World!';
}

ウィジェット

ウィジェットでは上記のProviderを監視して、ローディング -> 文字列表示を行うようにします。

sample_text.dart
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'sample_provider.dart';

class SampleText extends ConsumerWidget {
  const SampleText({super.key});

  
  Widget build(BuildContext context, WidgetRef ref) {
    final result = ref.watch(sampleStateProvider);  // 監視

    // この書き方はswitch式と呼ばれるもので、Riverpod ver.3.0以降で推奨されています
    switch (result) {
      case AsyncData(:final value):
        return Text(value);
      case AsyncError(:final error):
        debugPrint('error: $error');
        return const Text('エラー');
      case _:
        return const CircularProgressIndicator();
    }
  }
}

ちなみに、main.dartは次のような想定です。

main.dart
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'sample_text.dart';

void main() {
  runApp(const ProviderScope(child: MainApp()));
}

class MainApp extends StatelessWidget {
  const MainApp({super.key});

  
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: Scaffold(
        body: Center(
          child: SampleText(),
        ),
      ),
    );
  }
}

テスト

ウィジェットテストではProviderをモックして特定の文字列をすぐに返すようにします。
ProviderScopeoverridesProviderのモックを設定することができます。

sample_text.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import '../lib/sample_provider.dart';
import '../lib/sample_text.dart';

// 正常表示のウィジェット
Widget createSuccessWidget() {
  return ProviderScope(
      overrides: [
        // Providerをモック(3秒待たずに、Stringを返す)
        sampleStateProvider.overrideWith(
          (_) => '完了',
        ),
      ],
      child: const MaterialApp(
        home: SampleText(),
      ));
}

// エラー表示のウィジェット
Widget createErrorWidget() {
  return ProviderScope(
      overrides: [
        // Providerをモック(エラー発生)
        sampleStateProvider.overrideWith(
          (_) {
            throw Exception('error');
          },
        ),
      ],
      child: const MaterialApp(
        home: SampleText(),
      ));
}

void main() {
  testWidgets('正常', (WidgetTester tester) async {
    await tester.pumpWidget(createSuccessWidget());
    await tester.pumpAndSettle();

    expect(find.text('完了'), findsOneWidget);
  });

  testWidgets('エラー', (WidgetTester tester) async {
    await tester.pumpWidget(createErrorWidget());
    await tester.pumpAndSettle();

    expect(find.text('エラー'), findsOneWidget);
  });
}

まとめ

Future Proverを用いたウィジェットの実装からテストまでの手順をまとめました。@riverpodを使うと簡単にProviderを定義できます。ウィジェットで用いる際はswitch式を使うことが推奨されています。
テストではProviderScopeoverridesProviderを指定するとモックできます。

Discussion