Testing
Testing is done with overrides. You need to place a ProviderScopeOverride and then specify the overrides argument with a list containing the providers followed by .overrideWith(provider).
An override replaces a provider entirely, and not just the value it holds: the provider passed to overrideWith is a regular provider, with its own create and dispose. The replacement must use the same provider kind as the provider being overridden: use Provider.withArgument to override a provider created with Provider.withArgument. This means a mock can, for instance, inject other providers through its context, exactly like the provider it replaces. The value of the original provider is never created at all.
An override also takes the place of the provider it replaces in every ProviderScope below the ProviderScopeOverride. The mock therefore has the very same lifecycle as the original provider: its value is created lazily where the value of the original provider would have been created, it is disposed when that ProviderScope is disposed, and there is one value per ProviderScope providing it.
Examples
The text displayed in the following example is “100” because overrides always take precedence.
testWidgets( '''ProviderScopeOverride should override providers''', (tester) async { final numberProvider = Provider<int>((context) => 0); await tester.pumpWidget( ProviderScopeOverride( overrides: [ numberProvider.overrideWith(Provider((context) => 100)), ], child: MaterialApp( home: ProviderScope( providers: [ numberProvider(), ], child: Builder( builder: (context) { final number = numberProvider.of(context); return Text(number.toString()); }, ), ), ), ), ); expect(find.text('100'), findsOneWidget);});Testing is possible also with providers that take an argument, and it is done the same exact way. The only difference is that the mock is an argument provider as well, and thus it receives the argument that has been specified in the widget tree.
testWidgets( '''ProviderScopeOverride should override argument providers''', (tester) async { final numberProvider = Provider.withArgument((context, int arg) => arg * 2); await tester.pumpWidget( ProviderScopeOverride( overrides: [ // `arg` is 1 here, i.e. the argument passed to the provider below numberProvider.overrideWith( Provider.withArgument((context, int arg) => arg + 7), ), ], child: MaterialApp( home: ProviderScope( providers: [ numberProvider(1), ], child: Builder( builder: (context) { final number = numberProvider.of(context); return Text(number.toString()); }, ), ), ), ), ); expect(find.text('8'), findsOneWidget);});Mocking a class
Since a mock is a full-fledged provider, the object it creates is built the same way as in production code. A typical override therefore looks like this:
class MockModel extends Model { // ...}
// ...
modelProvider.overrideWith(Provider<Model>((context) => MockModel())),Ignoring the value of a provider
If a test does not care about the value of a provider, but the provider still has to be present, an override can also be used to make its creation cheap:
analyticsProvider.overrideWith(Provider((context) => NoopAnalytics())),Differences between the two kinds of overrides
Overrides of providers and overrides of argument providers behave almost identically. The only difference comes from the fact that an argument provider can only be instantiated where its argument is known, i.e. where it is inserted into the widget tree:
Provider | Provider.withArgument | |
|---|---|---|
| Where the value lives | in the ProviderScope providing it | in the ProviderScope providing it |
| Number of values created | one per ProviderScope providing it | one per ProviderScope providing it |
Requires a ProviderScope providing it | no | yes |
A plain Provider does not strictly need a ProviderScope providing it, because the ProviderScopeOverride can provide the mock itself. This is handy when testing a single widget in isolation:
await tester.pumpWidget( ProviderScopeOverride( overrides: [modelProvider.overrideWith(Provider<Model>((_) => MockModel()))], // No ProviderScope needed: the override provides the mock. child: const MaterialApp(home: WidgetUnderTest()), ),);