flutterdartflutter-testriverpod

Riverpod - How to access a provider in a test?


I have this snippet:

final countProvider = StateProvider<int>((ref) {
  return 0;
});

class CountWidget extends ConsumerWidget {
  const CountWidget();

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final count = ref.watch(countProvider);
    return Column(
      children: [
        Text(count.toString()),
        IconButton(
          icon: const Icon(Icons.add),
          onPressed: () {
            ref.read(countProvider.notifier).state++;
          },
        ),
      ],
    );
  }
}

This is a pretty simplified code, but the idea is that it is using a state provider.

I would like to write a test and verify that, after some actions, the provider is in a specific state (without relying on the UI, here I could use find.text(), but my state could be much more complex).

I would like to access the model in my test after pumping my widget:

await tester.pumpWidget(const CountWidget());

await tester.tap();
await tester.pump();

// ... Some other actions.

final currentCountState = // ?
expect(currentCountState, 3); // For example.

How can I do that?


Solution

  • Solution 1

    ProviderScope has a static method .containerOf which returns the ProviderContainer of the closer ProviderScope of the current context.

    Let's say you want to get the WidgetRef ref associated/active for the widget with a key Key('key'), you can obtain its context with tester.element. Then you can use ProviderScope.containerOf:

    final context = tester.element(find.byType(Key('key')));
    final providerContainer = ProviderScope.containerOf(context); // <- Your `ref`.
    

    Solution 2

    Here, CountWidget extends ConsumerWidget which extends ConsumerStatefulWidget which extends StatefulWidget.

    In riverpod's code, we can see that the created state is actually a _ConsumerState:

    class _ConsumerState extends ConsumerState<ConsumerWidget> {
      @override
      WidgetRef get ref => context as WidgetRef;
    
      @override
      Widget build(BuildContext context) {
        return widget.build(context, ref);
      }
    }
    

    context and ref are actually the same object.

    And this is because in ConsumerStatefulWidget:

    /// A [StatefulWidget] that can read providers.
    abstract class ConsumerStatefulWidget extends StatefulWidget {
      /// A [StatefulWidget] that can read providers.
      const ConsumerStatefulWidget({Key? key}) : super(key: key);
    
      @override
      // ignore: no_logic_in_create_state
      ConsumerState createState();
    
      @override
      ConsumerStatefulElement createElement() {
        return ConsumerStatefulElement(this);
      }
    }
    

    The associated element (which is the what is used for the context is a ConsumerStatefulElement:

    class ConsumerStatefulElement extends StatefulElement implements WidgetRef {
      // ...
    }
    

    So in the test, you can use tester.element to get ref:

    await tester.pumpWidget(const CountWidget());
    
    await tester.tap();
    await tester.pump();
    
    // ... Some other actions.
    
    final ref = tester.element<ConsumerStatefulElement>(find.byType(CountWidget));
    final currentCountState = ref.read(countProvider);
    expect(currentCountState, 3); // For example.