mirror of
https://github.com/rrousselGit/riverpod.git
synced 2025-08-14 17:41:48 +08:00
163 lines
5.8 KiB
Plaintext
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} />
|