Sparround

Testing Riverpod: ProviderContainer and overrides

Riverpod's testing model rests on two things: the container and overrides.

  • ProviderContainer.test() — per the docs it is part of Riverpod in 3.0: it creates a container for the test and disposes it automatically when the test ends. The docs' warning is blunt: do not share containers between tests, and inside tests use ProviderContainer.test() rather than ProviderContainer directly.
  • `overrides` — any provider can be mocked with no extra setup: ProviderContainer.test(overrides: [...]), or ProviderScope(overrides: [...]) in a widget test.

For reading: container.read(provider) gives the current value, while container.listen(provider, listener) creates a listener and prevents automatic disposal — the docs advise care with read on auto-disposed providers and suggest listen instead. To await an async provider's result, read provider.future.

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

class FakeTodoRepository implements TodoRepository {
  FakeTodoRepository(this.items);
  final List<Todo> items;

  @override
  Future<List<Todo>> fetchAll() async => items;
}

void main() {
  test('todosProvider repository-dən gələn siyahını qaytarır', () async {
    final container = ProviderContainer.test(
      overrides: [
        // İstənilən provider override oluna bilər.
        todoRepositoryProvider.overrideWithValue(
          FakeTodoRepository([const Todo(id: '1', title: 'Süd al')]),
        ),
      ],
    );

    // Async provider-in nəticəsini future kimi gözləyirik.
    await expectLater(
      container.read(todosProvider.future),
      completion(hasLength(1)),
    );
  });

  test('add() siyahıya element əlavə edir', () async {
    final container = ProviderContainer.test(
      overrides: [
        todoRepositoryProvider.overrideWithValue(FakeTodoRepository(const [])),
      ],
    );

    // listen: autoDispose provider-in test müddətində yaşamasını təmin edir.
    final subscription = container.listen(todosProvider, (_, _) {});
    await container.read(todosProvider.future);

    await container.read(todosProvider.notifier).add('Yeni tapşırıq');

    expect(subscription.read().value, hasLength(1));
  });
}

A unit test: the container, an override and awaiting the async result.

dart
void main() {
  testWidgets('siyahı boş olduqda boş vəziyyət göstərilir', (tester) async {
    await tester.pumpWidget(
      ProviderScope(
        overrides: [
          todoRepositoryProvider.overrideWithValue(FakeTodoRepository(const [])),
        ],
        child: const MaterialApp(home: TodoPage()),
      ),
    );

    // İlk frame: loading.
    expect(find.byType(CircularProgressIndicator), findsOneWidget);

    await tester.pumpAndSettle();

    expect(find.text('Hələ tapşırıq yoxdur'), findsOneWidget);
  });

  testWidgets('xəta halında yenidən yoxla düyməsi görünür', (tester) async {
    await tester.pumpWidget(
      ProviderScope(
        overrides: [
          // Provider-i birbaşa xəta atan implementasiya ilə əvəz edirik.
          todosProvider.overrideWith(() => ThrowingTodosNotifier()),
        ],
        child: const MaterialApp(home: TodoPage()),
      ),
    );

    await tester.pumpAndSettle();

    expect(find.text('Yenidən yoxla'), findsOneWidget);
  });
}

A widget test: a fake dependency through `ProviderScope(overrides: ...)`.

GoalAPI
An isolated container for the test`ProviderContainer.test()` — disposed automatically at the end of the test
Read the current value`container.read(provider)`
Observe changes and prevent autoDispose`container.listen(provider, (prev, next) {...})`
Await an async result`await container.read(provider.future)`
Replace a dependency`overrideWith(...)` / `overrideWithValue(...)`
Overriding in a widget test`ProviderScope(overrides: [...])`

An advantage worth stressing in an interview: with the Provider package a widget test has to rebuild the provider tree by hand, and with BLoC you write a MockBloc — whereas in Riverpod every provider can be overridden with no extra setup. That shortens test code especially when replacing the repository/service layer: the UI is untouched and only the dependency changes.

📚 Sources and documentation