Flutter Unit Testing with Riverpod and Mocktail
In today’s article, we will implement unit test using Riverpod state management which is a spiritual successor and an anagram of Provider.
I assume the reader has previous knowledge using Flutter, Dart language and Riverpod basics.
The Flutter version i am using in this example is: (Channel stable, 3.3.6)
Getting started
In this example, we will be using Todo-list most known example, and here is what we will cover:
1- Implement and provide our Sharedpreference using Riverpod’s Provider, (which we will Mock it later to apply our unit test).
2- Instantiate Sharedpreference service main dart file.
3- Create a Todo class provider that contains a Future<List<TodoModel>> type function GetTodoList.
4- Implement UI to display the TodoList.
5- Create unit-test by mocking Sharepreference then test/verify our GetTodoList function.
Let’s start with the first step:
Implementation
Step 1: Add the dependencies
Add dependencies to pubspec.yaml file.
dependencies:
flutter:
sdk: flutter
shared_preferences:
flutter_riverpod: ^2.0.2
equatable: ^2.0.5
dev_dependencies:
flutter_test:
sdk: flutter
mocktail: ^0.3.0
Step 2: Import shared_preferences.dart
import 'package:shared_preferences/shared_preferences.dart';
Instantiation
In this step, we need to do two things: provide Sharedpreference using Riverpod’s provider to call it later for specific use & override its value in the ProviderScope with instance value.
final sharedPreferencesProvider =
Provider<SharedPreferences>((ref) => throw UnimplementedError());
Calling Sharedpreference’s provider and overriding its value with Sharedpreferences instance in main.dart file.
main() async {
WidgetsFlutterBinding.ensureInitialized();
final sharedPreferences = await SharedPreferences.getInstance();
runApp(
ProviderScope(
overrides: [
sharedPreferencesProvider.overrideWithValue(sharedPreferences),
],
child: const SampleApp(),
),
);
}
Creating TodoList class provider
In this file, we are implementing a todoListFutureProvider that asynchronously creates a single value.
We will read todoListFutureProvider from our UI to be able to read the state of the future synchronously, handle the loading/error states, and rebuild when the future completes.
In line 9, we are reading from [SharedPreferencesProvider] and doing a null check, if the value is null we fill our sharedPreference with value otherwise we are simply returning the data.
As you noticed in line 7, the [FutureProvider] has a [List<TodoModel>] data type.
Our TodoModel will look like this:
Implement UI
Our UI will be a simple page by listening to the todoListFutureProvider inside a widget and returning an [AsyncValue], this will automatically rebuild the UI when the [Future] completes.
In line 10, we are reading the todoListFutureProvider using the ref object which is what allows us to interact with providers, be it from a widget or another provider.
Create unit-test
N.B: in this tuto we are using Mocktail instead of Mockito, and the reason for that is we need to verify non-nullable return types otherwise it will cause test failures using the Mockito package, additionally we won’t need any manual mocks or code generation.
In our test, we want to do two things:
1- Verify that listener is called before and after instantiation.
2- Expect length from todoListFutureProvider.
As i mentioned before, our test will fail if we verify the non-nullable return types, in our case, our return type is AsyncData<List<TodoModel>>([]), we must use registerFallbackValue
to provide a default return value as we did in line 20. Thanks to Mocktail
If we didn’t set up the registerFallbackValue we will be expecting the following error:
Bad state: A test tried to use `any` or `captureAny` on a parameter of type `AsyncValue<List<TodoModel>>`, but
registerFallbackValue was not previously called to register a fallback value for `AsyncValue<List<TodoModel>>`.
In Line 11, we are mocking our Sharepreference and using it to stub method to return success in Line 34.
After that, we need to override the value of [sharedPreferencesProvider] with [mockSharedPreferences] using ProviderContainer, Line 37.
By doing that, we will be able to listen to the provider (todoListFutureProvider) and call [listener] whenever its value changes, Line 43.
Now we will start the verification process, as you see in the code above we are implementing two verification:
1- First, before reading from todoListFutureProvider from our container in Line 52, we are verifying our listener which will be expecting an empty TodoList.
2- Secondly, after reading from todoListFutureProvider we are using verifyInOrder Line 55.
Lastly, in Line 61, we are using the expect method to expect the length of todoListFutureProvider, in our case we stubbed earlier our method with values to return 2 Todos Line 27, so we are expecting 2.
Full Source code: Here, don’t forget to star this repository if you found this article helpful.
https://github.com/ayoubboumzebra/riverpod_unit_test_mock_shared_preference
Conclusion:
Writing unit tests may look hard, but it is worth it because it will save you a lot of time.
If you have any questions, please leave a comment below or follow me on GitHub.