用 mockito 测试 Riverpod 的正确方法是啥
Posted
技术标签:
【中文标题】用 mockito 测试 Riverpod 的正确方法是啥【英文标题】:what is the correct approach to test riverpod with mockito用 mockito 测试 Riverpod 的正确方法是什么 【发布时间】:2021-03-24 11:42:05 【问题描述】:用 mockito 测试 Riverpod 的正确方法是什么?
运行上面的代码,
/// ### 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的新手,显然我错过了一些东西 我试着关注this
【问题讨论】:
【参考方案1】:我发现我在使用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 = 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(...),
);
【讨论】:
我收到错误The getter 'state' isn't defined for the type 'StateNotifierProvider...
还有其他人吗?【参考方案2】:
您的代码中有 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) 数据为 null,并且由于调用 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
);
);
【讨论】:
感谢您的回答和您的代码示例,虽然它不使用 mockito 我想使用 mockito 来获得额外的功能,例如 verify / verfynomoreinteractions,现在我将遵循基本测试文档,也许我稍后会弄明白 我认为您正在使用 mockito,因为您已经导入了 import 'package:mockito/mockito.dart';在您的代码中,无论哪种方式,因为它是假的,您可以创建自己的扩展 FutureDelegate 并使用 throw 覆盖的类,它应该在没有 mockito 的情况下工作【参考方案3】:还可以考虑通过在*** ProviderScope 中使用 Override 来模拟各种提供程序。这就是 override 可以做得很好的地方。
【讨论】:
首先感谢您抽出宝贵的时间来回答,我想使用 mockito 来实现 verify / verfynomoreinteractions 等额外功能,但我认为您是对的,我现在可能会解决,仍然希望弄清楚如何在未来使用这两种方法以上是关于用 mockito 测试 Riverpod 的正确方法是啥的主要内容,如果未能解决你的问题,请参考以下文章