Why Choose Riverpod? — Testing
This post is based off a presentation I did at Devfest 2023 in Cape Town on 23 Nov 2023. VIDEO | PRESENTATION
Previously we covered the ProviderScope
, the secret sauce as I like to call it, finally we are going to take a look at just how much everything simplifies our lives when it comes to testing.
Testing is probably one of my favourite reasons to use Riverpod, just because of how simple it makes it.
Widget testing is where we bring back our ProviderScope
extension PumpApp on WidgetTester {
Future<void> pumpApp(
Widget widget, [
List<Override> overrides = const [],
]) async {
return pumpWidget(
ProviderScope(
overrides: overrides,
child: MaterialApp(
home: Scaffold(
body: widget,
),
),
),
);
}
}
Above is an extension I put together to make my life easier when it comes to testing, I make use of pumpApp
instead of pumpWidget
as it extends on pubpWidget
but also allows me to easily wrap some boilerplate around my tests.
In the above example, I am wrapping every widget that gets tested with ProviderScope
and also add support for optionally passing in an array of overrides to said ProviderScope
.
An extension like this can also easily include things like materialApp
, Scaffold
and even navigation observers.
late MockConnectivityProvider mockConnectivityProvider;
setUp(() {
mockConnectivityProvider = MockConnectivityProvider();
when(() => mockConnectivityProvider.isOnline).thenReturn(true);
});
testWidgets('should show OnlineIndicator', (tester) async {
await tester.pumpApp(
widget,
[
connectivityProvider.overrideWith((_) => mockConnectivityProvider),
],
);
expect(OnlineIndicator, findsOneWidget);
});
Here we have a simple examples where we testing the OnlineIndicator
widget which replies on the connectivityProvider
, which is a network listener to verify that the device is both connected and is able to access the Internet.
This connectivityProvider
as you may have guess is also a Riverpod provider, and as you can see in the setUp
function we are mocking the response from that providers isOnline
method to true
, aka the devise in online.
With the pumpApp
we are overriding the provider with our mocked one, and then in the test we simply verify that the OnlineIndicator
does in fact renders.
Mocking State
When it comes to mocking the dat from any Riverpod StateNotifier
, it is about as simple:
await tester.pumpApp(
widget,
[
createTripProvider.overrideWith((_) => mockCreateTripNotifier
..state = CreateTripState.initial().copyWith(jobs: mockJobs)),
],
);
Much like above we would have setup a MockTripNotifierClass
, which would look slightly different to a normal mock:
class MockCreateTripNotifier extends StateNotifier<CreateTripState>
with Mock
implements CreateTripNotifier {
MockCreateTripNotifier() : super(CreateTripState());
}
Then e simply access the state directly within the override and assign it an updated value.
External Packages
Another reason I love Riverpod for testing is when it comes to external packages and their ability to allow them to be very easily mocked.
One that is extensively used throughout our application is Firestore, to normally be able to mack this it would need to be an argument on the class or function whereby the instance is passed into.
Riverpod makes that much simpler. which is why within our application we have firestoreProvider
which gets overridden with FirebaseFirestore.instance
within the ProviderScope
during the applications startup.
As you can by now tell, this means that Firestore
can be directly accessed anywhere we are making use of Riverpod.
//main
firestoreProvider.overrideWith((_) => FirebaseFirestore.instance),
//Mock
late MockDocumentReference mockDocumentReference;
mockDocumentReference = MockDocumentReference();
overrides: [
firestoreProvider.overrideWith((_) => mockFirestore),
]
when(() => mockFirestore.doc('drivers/driver_id'))
.thenReturn(mockDocumentReference);
when(() => mockDocumentReference.update(payload))
.thenAnswer((_) async => Future.value());
Now when looking from a testing perspective and considering the other examples with the excerpts in the code above you can see how much simpler Riverpod makes mocking an external packages, especially ones like the Firebase packages that are exposing singletons.
I hope you found this interesting, and if you have any questions, comments, or improvements, feel free to drop a comment. Enjoy your development journey :D
Thanks for reading.
Originally published at https://remelehane.dev on March 12, 2024.