riverpod+flutter テスト中でのstateの取得方法


riverpodを使っているときのユニットテスト・ウィジェットテストの書き方にハマったのでメモします。
ここではStateNotifierを使っているテスト中にstateの中身を比較する例を書いています。

ウィジェットテストでのstateの取得方法

// BuildContextを取得
      final BuildContext context = tester.element(find
          .byWidgetPredicate((Widget widget) => widget is SomeScreen));
// ProviderContainerを取得
final container = ProviderScope.containerOf(context);
// ProviderContainerからproviderをreadしてsomeStateを取得
container.read(someProvider);

単体テストでのstateの取得方法

// ProviderContainerを設定
final target = ProviderContainer(
    overrides: [
    // テスト初期値設定
    someStateProvider.overrideWithValue(
    SomeStateNotifier(
	    ...
            ),
          ),
        ],
     );
// ProviderContainerからproviderをreadしてsomeStateを取得
expect(container.read(emailChangeProvider);

実装&テストの例

実装部分

@freezed
class SomeState with _$SomeStateState {
  const SomeStateState._();
  factory SomeStateState({
    @Default('') String someField1,
    @Default(0) int someField2,
  }) = _SomeState;
}

final someProvider =
    StateNotifierProvider<SomeStateNotifier, SomeState>(
  (ref) => SomeStateNotifier(
    someState: SomeState(),
  ),
);

class SomeStateNotifier extends StateNotifier<SomeState> {
  SomeStateNotifier({required SomeState someState})
      : super(someState);

  void onSomefield1Changed(String someField1) {
    state = state.copyWith(someField1: someField1);
  }
}

class SomeScreen extends ConsumerWidget {
  SomeScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final someNotifier = ref.watch(someProvider.notifier);
    final someState = ref.watch(emailChangeProvider);
    return Scaffold(
      appBar: AppBar(
        title: Text(
          someState.field1,
          style: Theme.of(context).textTheme.headline6,
        ),
      ),
      ....
  }
}

テスト内でのsomeStateの値比較

void main() {
    // ウィジェットテスト
    testWidgets('someField', (WidgetTester tester) async {
      final target = ProviderScope(
        overrides: [
	  // テスト初期値設定
          someStateProvider.overrideWithValue(
            SomeStateNotifier(
              someState: SomeState(
                field1: 'field1',
                field2: 'field2',
              ),
            ),
          ),
        ],
        child: MaterialApp(
          home: SomeScreen(),
        ),
      );
      //widget生成
      await tester.pumpWidget(
        target,
      );
      await tester.pumpAndSettle();
      // BuildContextを取得
      final BuildContext context = tester.element(find
          .byWidgetPredicate((Widget widget) => widget is SomeScreen));
      // ProviderContainerを取得
      final container = ProviderScope.containerOf(context);
      // ProviderContainerからproviderをreadしてsomeStateを取得
      expect(container.read(someProvider).field1, field1);
    });
    
    statenotifierのユニットテスト
    test('field1を受け取ってstateを更新する', () {
        const field1 = '[email protected]';
	// ProviderContainerを設定
        final target = ProviderContainer(
          overrides: [
	  // テスト初期値設定
          someStateProvider.overrideWithValue(
            SomeStateNotifier(
              someState: SomeState(
                field1: 'field1',
                field2: 'field2',
              ),
            ),
          ),
        ],
        );
        target.read(someStateProvider.notifier).onSomefield1Changed(field1);
	// ProviderContainerからproviderをreadしてsomeStateを取得
        expect(container.read(emailChangeProvider).newEmail, field1);
      });
}