как правильно тестировать Riverpod с помощью mockito
как правильно протестировать Riverpod с помощью mockito?
запуск кода выше,
/// ### 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>()]);
});
});
}
запуск теста возвращает
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>
Я новичок в riverpod, очевидно , я что - то не хватает , я пытался следовать этим
3 ответа
Я обнаружил, что у меня были дополнительные ошибки, особенно при использовании
StateNotifierProvider
. Хитрость заключалась в том, чтобы не только переопределить StateNotifierProvider, но и его
state
свойство (которое является объектом StateNotifierStateProvider).
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 = SomeStateNotifier();
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(...),
);
}
Также рассмотрите возможность издевательства над различными поставщиками с помощью Override в вашем ProviderScope верхнего уровня. Это то, что переопределение может делать хорошо.
В вашем коде 2 ошибки
Вы пытаетесь проверить ошибку выброса, поэтому вам следует использовать
thenThrow
вместо
thenAnswer
, но поскольку вы переопределяете метод смешивания, я бы рекомендовал вместо использования
Mock
использовать
Fake
(из той же библиотеки mockito), чтобы переопределить методы, а затем выбросить его, как хотите
class MockDelegate extends Fake implements FutureDelegate<String> {
@override
Future<String> call() async {
throw NullThrownError; //now you can throw whatever you want
}
}
И вторая проблема (и та, о которой вас предупреждает ваш код) заключается в том, что вы намеренно бросаете, поэтому вместо этого вы должны ожидать AsyncError, поэтому вызывая
container.read(stringProvider).data.value
является ошибкой из-за чтения документации по riverpod:
При вызове данных:
Текущие данные или null, если загрузка / ошибка.
поэтому, если вы ожидаете ошибки (AsyncError), данные равны нулю, и из-за этого вызова
data.value
это то же самое, что писать
null.value
какая ошибка вы испытываете
Вот код, который вы можете попробовать:
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
});
});
}