3

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?

Valentin Vignal
  • 6,151
  • 2
  • 33
  • 73

2 Answers2

5

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 providerContainter = 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.
Valentin Vignal
  • 6,151
  • 2
  • 33
  • 73
1

The accepted answer is a litte bit hacky and will throw an error if the provider is read when it is already disposed. The correct way is to explicitly use ProviderContainer and UncontrollerProviderScope in test. By using this we can use the provider outside of a widget as well.

// make sure riverpod is imported
import 'package:flutter_riverpod/flutter_riverpod.dart';

test('outside of widget', () {
   final container = ProviderContainer(
     overrides: [
       // define your overrides here if required
     ]
   );

   final count = container.read(countProvider);
   expect(count, 0);
});

testWidgets('Test the widget', () async {
   final container = ProviderContainer();

   await tester.pumpWidget(
      UncontrolledProviderScope(
        container: container,
        child: const CountWidget(),
      )
   );

   await tester.tap(find.byIcon(Icons.add));
   final currentCount = container.read(countProvider);
   expect(currentCount, 1);
});

References:

Alexander Dischberg
  • 1,890
  • 2
  • 13
  • 26
  • The issue with that is that if your pumped widget introduces a `ProviderScope` with an override in the widget tree, you won't be able to access the provider with your method – Valentin Vignal Sep 11 '22 at 11:03
  • @ValentinVignal True that. I just put the `overrides` just for the sake of completeness. It's not necessary to override unless you needed to. Like from your given example, it doesn't need an override. – Alexander Dischberg Sep 11 '22 at 11:38
  • 1
    It is actually pretty common to override when you want to scope your models. You can look at the [riverpod examples](https://github.com/rrousselGit/riverpod/tree/master/examples/marvel) – Valentin Vignal Sep 11 '22 at 12:19
  • Sure, if you are talking about using it to scope models in you view. Heck, I override a lot instead of mocking in test as well. – Alexander Dischberg Sep 11 '22 at 16:17