Files
2025-06-08 21:46:35 +02:00

163 lines
5.8 KiB
Plaintext

---
title: Testing your providers
version: 4
---
import { AutoSnippet } from "/src/components/CodeSnippet";
import unitTest from "!!raw-loader!./testing/unit_test.dart";
import widgetTest from "!!raw-loader!./testing/widget_test.dart";
import fullWidgetTest from "!!raw-loader!./testing/full_widget_test.dart";
import testerContainer from "!!raw-loader!./testing/tester_container.dart";
import providerToMock from "./testing/provider_to_mock";
import mockProvider from "!!raw-loader!./testing/mock_provider.dart";
import autoDisposeListen from "!!raw-loader!./testing/auto_dispose_listen.dart";
import listenProvider from "!!raw-loader!./testing/listen_provider.dart";
import awaitFuture from "!!raw-loader!./testing/await_future.dart";
import notifierMock from "./testing/notifier_mock";
import notifierUsage from "!!raw-loader!./testing/notifier_usage.dart";
A core part of the Riverpod API is the ability to test your providers in isolation.
For a proper test suite, there are a few challenges to overcome:
- Tests should not share state. This means that new tests should
not be affected by the previous tests.
- Tests should give us the ability to mock certain functionalities
to achieve the desired state.
- The test environment should be as close as possible to the real
environment.
Fortunately, Riverpod makes it easy to achieve all of these goals.
## Setting up a test
When defining a test with Riverpod, there are two main scenarios:
- Unit tests, usually with no Flutter dependency.
This can be useful for testing the behavior of a provider in isolation.
- Widget tests, usually with a Flutter dependency.
This can be useful for testing the behavior of a widget that uses a provider.
### Unit tests
Unit tests are defined using the `test` function from [package:test](https://pub.dev/packages/test).
The main difference with any other test is that we will want to create
a `ProviderContainer` object. This object will enable our test to interact
with providers.
A typical test using `ProviderContainer` will look like:
<AutoSnippet raw={unitTest} />
Now that we have a ProviderContainer, we can use it to read providers using:
- `container.read`, to read the current value of a provider.
- `container.listen`, to listen to a provider and be notified of changes.
:::caution
Be careful when using `container.read` when providers are automatically disposed.
If your provider is not listened to, chances are that its state will get destroyed
in the middle of your test.
In that case, consider using `container.listen`.
Its return value enables reading the current value of provider anyway,
but will also ensure that the provider is not disposed in the middle of your test:
<AutoSnippet raw={autoDisposeListen} />
:::
### Widget tests
Widget tests are defined using the `testWidgets` function from [package:flutter_test](https://pub.dev/packages/flutter_test).
In this case, the main difference with usual Widget tests is that we must add
a `ProviderScope` widget at the root of `tester.pumpWidget`:
<AutoSnippet raw={widgetTest} />
This is similar to what we do when we enable Riverpod in our Flutter app.
Then, we can use `tester` to interact with our widget.
Alternatively if you want to interact with providers, you can obtain
a `ProviderContainer`.
One can be obtained using `tester.container()`.
By using `tester`, we can therefore write the following:
<AutoSnippet raw={testerContainer} />
We can then use it to read providers. Here's a full example:
<AutoSnippet raw={fullWidgetTest} />
## Mocking providers
So far, we've seen how to set up a test and basic interactions with providers.
However, in some cases, we may want to mock a provider.
The cool part: All providers can be mocked by default, without any additional setup.
This is possible by specifying the `overrides` parameter on either
`ProviderScope` or `ProviderContainer`.
Consider the following provider:
<AutoSnippet {...providerToMock} />
We can mock it using:
<AutoSnippet raw={mockProvider} />
## Spying on changes in a provider
Since we obtained a `ProviderContainer` in our tests, it is possible to
use it to "listen" to a provider:
<AutoSnippet raw={listenProvider} />
You can then combine this with packages such as [mockito](https://pub.dev/packages/mockito)
or [mocktail](https://pub.dev/packages/mocktail) to use their `verify` API.
Or more simply, you can add all changes in a list and assert on it.
## Awaiting asynchronous providers
In Riverpod, it is very common for providers to return a Future/Stream.
In that case, chances are that our tests need to await for that asynchronous operation
to be completed.
One way to do so is to read the `.future` of a provider:
<AutoSnippet raw={awaitFuture} />
## Mocking Notifiers
It is generally discouraged to mock Notifiers. This is because Notifiers cannot be
instantiated on their own, and only work when used as part of a Provider.
Instead, you should likely introduce a level of abstraction in the logic of your
Notifier, such that you can mock that abstraction.
For instance, rather than mocking a Notifier, you could mock a "repository"
that the Notifier uses to fetch data from.
If you insist on mocking a Notifier, there is a special consideration
to create such a mock: Your mock must subclass the original Notifier
base class: You cannot "implement" Notifier, as this would break the interface.
As such, when mocking a Notifier, instead of writing the following mockito code:
```dart
class MyNotifierMock with Mock implements MyNotifier {}
```
You should instead write:
<AutoSnippet {...notifierMock} />
:::info
If using code-generation, for the above to work, your mock will have to
be placed in the same file as the Notifier you are mocking.
Otherwise you would not have access to the `_$MyNotifier` class.
Then, to use your notifier you could do:
<AutoSnippet raw={notifierUsage} />