Bidev

Testing Riverpod Providers: A Practical Setup

Bilal Fali3 min read
Testing Riverpod Providers: A Practical Setup

Riverpod providers are genuinely easier to test than the state management approaches that came before them, mostly because dependency overriding is a first-class feature instead of something you bolt on with a mocking library. The gap I see most often isn't that testing is hard, it's that developers don't reach for ProviderContainer directly and instead try to test through widgets when a plain unit test would be faster and clearer.

Start with ProviderContainer, not a widget test

If you're testing the logic inside a provider, not how a widget reacts to it, you don't need WidgetTester at all:

import 'package:flutter_test/flutter_test.dart';
import 'package:riverpod/riverpod.dart';

void main() {
  test('counterProvider increments', () {
    final container = ProviderContainer();
    addTearDown(container.dispose);

    expect(container.read(counterProvider), 0);

    container.read(counterProvider.notifier).increment();

    expect(container.read(counterProvider), 1);
  });
}

The addTearDown(container.dispose) line is easy to skip and shouldn't be. Without it, containers from earlier tests can leak state into later ones in the same test file, producing failures that look like flakiness but are actually just tests interfering with each other through un-disposed containers.

Override dependencies instead of mocking the provider itself

The pattern that actually matters for real apps is overriding a dependency (a repository, an API client) rather than trying to fake the provider's own behavior:

test('userProvider returns the repository\'s user', () async {
  final container = ProviderContainer(
    overrides: [
      userRepositoryProvider.overrideWithValue(FakeUserRepository()),
    ],
  );
  addTearDown(container.dispose);

  final user = await container.read(userProvider.future);

  expect(user.name, 'Test User');
});

This is the actual advantage Riverpod has here over patterns that require a mocking framework to intercept a singleton or a static call. The provider you're testing doesn't change; you swap out what it depends on, and the real provider logic runs against a fake dependency instead of a real one.

Testing AsyncNotifier: assert on the whole AsyncValue, not just the data

A common mistake is testing only the success path and ignoring that AsyncValue has three states your UI actually has to handle:

test('profileProvider shows loading then data', () async {
  final container = ProviderContainer(
    overrides: [profileRepositoryProvider.overrideWithValue(FakeProfileRepository())],
  );
  addTearDown(container.dispose);

  final sub = container.listen(profileProvider, (_, __) {});

  expect(container.read(profileProvider), const AsyncLoading<Profile>());

  await container.read(profileProvider.future);

  expect(container.read(profileProvider).value?.name, 'Test User');
  sub.close();
});

Testing the loading state explicitly catches a real class of bugs: a provider that technically returns the right data eventually but never emits a proper loading state along the way, which shows up in production as a UI that flashes empty content or skips a loading indicator the design actually calls for.

The flakiness trap: shared containers across tests

If tests pass individually but fail when run together, the first thing to check is whether a ProviderContainer is being reused or not disposed between tests. Riverpod containers hold real state, and state that survives past the test that created it is the most common source of "this test is flaky" reports that turn out to have a completely deterministic cause once you look at container lifecycle instead of assuming timing is the problem.

For a broader comparison of Riverpod against the alternative most teams weigh it against, see Riverpod vs Bloc.

Share this

Skip the boilerplate

Production-ready Flutter starter kit with Firebase Auth, Firestore, Cloud Functions, push notifications, and Clean Architecture — ship your app in days, not months.

Get Flutter Firebase Kit$5

Did this article save you time?

I write these for free. If it helped, a coffee keeps me going — and more articles coming.

Buy me a coffee

Comments

Comments

Leave a comment

0/2000

Comments appear after review.