Back to Riverpod

Testing your providers

website/docs/how_to/testing.mdx

2.0.0-dev.95.7 KB
Original Source

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.

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.

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 or 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} />