what is the correct approach to test riverpod with mockito
Solution 1
I found that I had some extra errors specifically when using StateNotifierProvider
. The trick was to not only override the StateNotifierProvider, but also its state
property (which is a StateNotifierStateProvider object).
class SomeState {
final bool didTheThing;
SomeState({this.didTheThing = false});
}
class SomeStateNotifier extends StateNotifier<SomeState> {
SomeStateNotifier() : super(SomeState());
bool doSomething() {
state = SomeState(didTheThing: true);
return true;
}
}
final someStateProvider = StateNotifierProvider<SomeStateNotifier>((ref) {
return SomeStateNotifier();
});
class MockStateNotifier extends Mock implements SomeStateNotifier {}
void main() {
final mockStateNotifier = MockStateNotifier();
when(mockStateNotifier.doSomething()).thenReturn(true);
final dummyState = SomeState(didTheThing: true); // This could also be mocked
ProviderScope(
overrides: [
someStateProvider.overrideWithValue(mockStateProvider), // This covers usages like "useProvider(someStateProvider)"
someStateProvider.state.overrideWithValue(dummyState), // This covers usages like "useProvider(someStateProvider.state)"
],
child: MaterialApp(...),
);
}
Solution 2
There are 2 errors in your code
You're trying to test a throw error, so you should use thenThrow
instead of thenAnswer
, but because you're overriding a mixing method I would recommend instead of using Mock
use Fake
(from the same mockito library) to override methods and then throw it as you want
class MockDelegate extends Fake implements FutureDelegate<String> {
@override
Future<String> call() async {
throw NullThrownError; //now you can throw whatever you want
}
}
And the second problem (and the one your code is warning you) is that you deliberately are throwing, so you should expect an AsyncError instead, so calling container.read(stringProvider).data.value
is an error because reading the riverpod documentation:
When calling data:
The current data, or null if in loading/error.
so if you're expecting an error (AsyncError) data is null, and because of that calling data.value
its the same as writing null.value
which is the error you're experiencing
This is the code you could try:
class MockDelegate extends Fake implements FutureDelegate<String> {
@override
Future<String> call() async {
throw NullThrownError;
}
}
void main() {
group('`stringProvider`', () {
final _delegate = MockDelegate();
test('WHEN `delegate` throws THEN `provider`return exception', () async {
final container = ProviderContainer(
overrides: [
stringProvider
.overrideWithProvider(FutureProvider((ref) => _delegate.call()))
],
);
expect(container.read(stringProvider), const AsyncValue<String>.loading());
container.read(stringProvider).data.value;
await Future<void>.value();
expect(container.read(stringProvider), isA<AsyncError>()); // you're expecting to be of type AsyncError because you're throwing
});
});
}
Solution 3
Also consider mocking out various providers by using an Override in your top level ProviderScope. That's what override can do quite well.
Francesco Iapicca
Self taught developer in love with Flutter and Dart
Updated on December 26, 2022Comments
-
Francesco Iapicca over 1 year
what is the correct approach to test riverpod with mockito?
running the code above,
/// ### edited snippets from production side ### /// not important, skip to the TEST below! /// this seems meaningless just because it is out of context mixin FutureDelegate<T> { Future<T> call(); } /// delegate implementation import '../../shared/delegate/future_delegate.dart'; const k_STRING_DELEGATE = StringDelegate(); class StringDelegate implements FutureDelegate<String> { const StringDelegate(); @override Future<String> call() async { /// ... returns a string at some point, not important now } } /// the future provider import 'package:hooks_riverpod/hooks_riverpod.dart'; import '<somewhere>/delegate.dart'; /// the code above final stringProvider = FutureProvider<String>((ref) => k_STRING_DELEGATE()); /// ### edited snippets from TEST side ### /// mocking the delegate import 'package:mockito/mockito.dart'; import '<see above>/future_delegate.dart'; class MockDelegate extends Mock implements FutureDelegate<String> {} /// actual test import 'package:flutter_test/flutter_test.dart'; import 'package:hooks_riverpod/all.dart'; import 'package:mockito/mockito.dart'; import '<somewhere in my project>/provider.dart'; import '../../domain/<somewhere>/mock_delegate.dart'; // <= the code above void main() { group('`stringProvider`', () { final _delegate = MockDelegate(); test('WHEN `delegate` throws THEN `provider`return exception', () async { when(_delegate.call()).thenAnswer((_) async { await Future.delayed(const Duration(seconds: 1)); throw 'ops'; }); final container = ProviderContainer( overrides: [ stringProvider .overrideWithProvider(FutureProvider((ref) => _delegate())) ], ); expect( container.read(stringProvider), const AsyncValue<String>.loading(), ); await Future<void>.value(); expect(container.read(stringProvider).data.value, [isA<Exception>()]); }); }); }
running the test returns
NoSuchMethodError: The getter 'value' was called on null. Receiver: null Tried calling: value dart:core Object.noSuchMethod src/logic/path/provider_test.dart 28:48 main.<fn>.<fn>
I'm new to riverpod, clearly I'm missing something I tried to follow this
-
Francesco Iapicca over 3 yearsfirst of all thank you for taking the time for answering, I'd like to use mockito for the extra features like verify / verfynomoreinteractions, but I think you are right, I might settle with that for now, still hoping to figure out how to use both in the future
-
Francesco Iapicca over 3 yearsthank you for the answer and your code sample, although it does not use mockito I'd like to use mockito for the extra features like verify / verfynomoreinteractions, for now I'll follow the basic testing from the docs, maybe I'll figure it out later
-
EdwynZN over 3 yearsI thoug you're using mockito because you already had import 'package:mockito/mockito.dart'; in your code, either way because its a fake you can create your own class that extends FutureDelegate and override with a throw, it should work without mockito
-
Matthew Rideout over 2 yearsI get the error
The getter 'state' isn't defined for the type 'StateNotifierProvider...
Anyone else? -
Hugo H almost 2 years@MatthewRideout Any luck on this? :)
-
Matthew Rideout almost 2 years@HugoH yes, please see this answer. Mockito / mocktail are not needed to mock state for StateNotifierProvider.