Flutter x RiverpodアプリのWidgetテストでProviderScopeを用いて初期値をオーバーライドする方法

# Flutterアプリのテストでproviderに初期値を指定する方法

FlutterでRiverpodを使用するアプリのwidget_testをする際にproviderの初期値を設定して  
特定のテストを実施しやすくする方法です。

## サンプルアプリ作成

flutter createで生成されるカウンターアプリを例に見てみます。  
今回 `hooks_riverpod` を使用してカウント数の状態管理にStateProviderを使い  
カウントが10まで達したらカウントをしないアプリを作ったとします。  

```dart
// カウントの状態管理をするStateProvider
final countStateProvider = StateProvider<int>((ref) => 0);

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends HookConsumerWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final counter = ref.watch(countStateProvider);
    return Scaffold(
      appBar: AppBar(
        title: const Text("Flutter Demo Home Page"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // カウントの上限を設定
          if (counter < 10) {
            ref.read(countStateProvider.state).state++;
          }
        },
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}
```
## 簡単なテスト作成

このアプリに対してのwidget_testを作っていきます。  
まずはボタンがタップされてカウントが1増加し画面の表示が切り替わっていることのテスト  

``` dart
  testWidgets('Count Up test', (WidgetTester tester) async {
    await tester.pumpWidget(const ProviderScope(
      child: MyApp(),
    ));
    expect(find.text('0'), findsOneWidget);
    expect(find.text('1'), findsNothing);
    await tester.tap(find.byIcon(Icons.add));
    await tester.pump();
    expect(find.text('0'), findsNothing);
    expect(find.text('1'), findsOneWidget);
  });
```

やっていることは カウント数の表示の有無の確認、 ボタンのタップと画面切り替えの待ち処理  
カウントが増加したことを確認です。  

## Providerに初期値を設定

では次にカウントが10まで達したら表示が切り替わらないことをテストします。  
この時

```dart
await tester.tap(find.byIcon(Icons.add));
```

の処理を10回回してもいいですが流石に冗長ですのでもっと簡単にかつきれいに書いていきます。  
やり方は簡単で `ProviderScope` で アプリ内のProviderの初期値をオーバーライドしてしまえば最初からカウントが10の状態からテストを始めることができます。  

```dart
  testWidgets('Count limit test', (WidgetTester tester) async {
    await tester.pumpWidget(ProviderScope(
      // overrides を使用して countStateProvider の初期値を10に設定する
      overrides: [countStateProvider.overrideWithValue(StateController(10))],
      child: const MyApp(),
    ));
    expect(find.text('10'), findsOneWidget);
    await tester.tap(find.byIcon(Icons.add));
    await tester.pump();
    expect(find.text('10'), findsOneWidget);
    expect(find.text('11'), findsNothing);
  });
```

`ProviderScope` の `overrides` プロパティの配列にオーバーライドするProviderとその値を渡してあげることで最初からカウントが10の状態でテストできます。(今回はStateProviderを使用しているため`.overrideWithValue` の引数には `StateController` を使っています。) . 

この方法を使うことでテストの際にモック化されたモデルや外部通信のモック/フェイクを使って自動テストをすることができます。