Unit testing in Flutter is a great way to ensure the quality of your code. It helps to identify bugs early on and reduce the risk of them making it to production. Unit tests can be written quickly and easily, and they provide a comprehensive overview of the code’s functionality.
Adding Dependencies
Add the following to pubspec.yaml:
dev_dependencies:
flutter_test:
sdk: flutter
riverpod_test: ^latest_version
mocktail: ^latest_version
Setting Up the Provider Container
Riverpod tests use ProviderContainer to scope providers without a widget tree:
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
late ProviderContainer container;
setUp(() {
container = ProviderContainer();
});
tearDown(() {
container.dispose();
});
test('counter increments', () {
expect(container.read(counterProvider), 0);
container.read(counterProvider.notifier).increment();
expect(container.read(counterProvider), 1);
});
}
Testing AsyncNotifier
For async providers, use expectAsync or await with container.read:
test('fetches user data', () async {
final container = ProviderContainer(
overrides: [
userRepositoryProvider.overrideWithValue(FakeUserRepository()),
],
);
final state = await container.read(userProvider.future);
expect(state.name, 'Ademola');
container.dispose();
});
Mocking Dependencies
Use mocktail to create fake implementations:
class FakeUserRepository extends Fake implements UserRepository {
@override
Future<User> getUser(String id) async {
return User(id: id, name: 'Ademola');
}
}
Best Practices
- Always dispose the
ProviderContainerintearDown - Override dependencies at the container level, not inside providers
- Test state transitions, not just final values
- Use
listenManualto track emissions over time
Unit tests for Riverpod are fast, isolated, and give you confidence that your state logic is correct before wiring it to the UI.