纯Dart环境下Riverpod Stream Provider测试问题求助
问题分析与修复方案
第一个测试:Bad state: No element 错误
原因
你的AtFhirListen的build方法存在两个关键问题:
- 调用
atClient.notificationService.subscribe().map(...)后,没有将生成的流内容输出到Provider的流中,导致整个Provider的流是空的,调用stream.first自然会报错“无元素”。 - 使用
map处理流元素时,内部返回的是async*生成的子流,但map会把每个元素转换成Stream<AtFhirNotification>,而不是展开这些子流的元素。需要用asyncExpand替代map来展开子流。
修复后的Provider代码
@Riverpod(keepAlive: true) class AtFhirListen extends _$AtFhirListen { @override Stream<AtFhirNotification> build(AtClient atClient) async* { // 使用yield* 将内部流的元素输出到Provider的流中 // 使用asyncExpand 展开每个元素生成的子流 yield* atClient.notificationService .subscribe(shouldDecrypt: true) .asyncExpand((AtNotification atNotification) async* { if (atNotification.value != null) { try { final AtFhirNotification atFhirNotification = AtFhirNotification.fromJsonString(atNotification.value!); yield atFhirNotification; } catch (exception) { // 可在此添加错误处理逻辑,比如记录日志或抛出错误 } } }); } }
修复后的第一个测试代码
此时原测试代码即可正常运行,因为Provider的流现在会输出正确的元素:
class MockAtClient extends Mock implements AtClient { @override final NotificationService notificationService = MockNotificationService(); } class MockNotificationService extends Mock implements NotificationService { @override Stream<AtNotification> subscribe({String? regex, bool? shouldDecrypt}) { return Stream<AtNotification>.fromIterable(<AtNotification>[ AtNotification('id1', 'key1', '@from1', '@to1', 12345, 'text', true), AtNotification('id2', 'key2', '@from2', '@to2', 12345, 'text', true, value: Dstu2ResourceNotification( dstu2.Patient(fhirId: dstu2.FhirId('id2'))) .toJsonString()), AtNotification('id3', 'key3', '@from3', '@to3', 12345, 'text', true), AtNotification('id4', 'key4', '@from4', '@to4', 12345, 'text', true), ]); } } void main() { final MockAtClient atClient = MockAtClient(); group('AtFhirListen', () { test('should return AtFhirNotification', () async { final Stream<AtFhirNotification> stream = AtFhirListen().build(atClient); expect( await stream.first, AtFhirNotification.dstu2Resource( dstu2.Patient(fhirId: dstu2.FhirId('id2')))); }); }); }
第二个测试:Used on a non-mockito object 错误
原因
Mockito的verify方法只能作用于Mockito生成的Mock对象,你直接创建的Listener实例不是Mock对象,因此无法被verify校验。
修复方案
创建一个继承自Mock的MockListener类,用于监听Provider的状态变化:
修复后的第二个测试代码
import 'package:mockito/mockito.dart'; import 'package:riverpod/riverpod.dart'; // 定义MockListener类,实现Listener接口并继承Mock class MockListener extends Mock implements Listener<AsyncValue<AtFhirNotification>> {} class MockAtClient extends Mock implements AtClient { @override final NotificationService notificationService = MockNotificationService(); } class MockNotificationService extends Mock implements NotificationService { @override Stream<AtNotification> subscribe({String? regex, bool? shouldDecrypt}) { return Stream<AtNotification>.fromIterable(<AtNotification>[ AtNotification('id2', 'key2', '@from2', '@to2', 12345, 'text', true, value: Dstu2ResourceNotification( dstu2.Patient(fhirId: dstu2.FhirId('id2'))) .toJsonString()), ]); } } void main() { group('AtFhirListen', () { test('should notify listener with AtFhirNotification', () async { final MockAtClient atClient = MockAtClient(); final ProviderContainer container = ProviderContainer(); final MockListener listener = MockListener(); container.listen( atFhirListenProvider(atClient), listener, fireImmediately: true, ); // 等待流完成一次事件循环,确保Listener收到通知 await Future<void>.delayed(Duration.zero); verify( () => listener( null, AsyncData<AtFhirNotification>( AtFhirNotification.dstu2Resource( dstu2.Patient(fhirId: dstu2.FhirId('id2')), ), ), ), ).called(1); }); }); }
额外优化:规范Mockito使用方式
建议使用Mockito的when方法来Stub依赖方法,而不是直接重写Mock类的方法,这样可以更灵活地控制Mock行为,还能验证方法的调用情况:
void main() { late MockAtClient atClient; late MockNotificationService mockNotificationService; setUp(() { mockNotificationService = MockNotificationService(); atClient = MockAtClient(); // Stub notificationService属性 when(() => atClient.notificationService).thenReturn(mockNotificationService); // Stub subscribe方法 when(() => mockNotificationService.subscribe(shouldDecrypt: true)).thenAnswer((_) => Stream<AtNotification>.fromIterable([ AtNotification('id2', 'key2', '@from2', '@to2', 12345, 'text', true, value: Dstu2ResourceNotification( dstu2.Patient(fhirId: dstu2.FhirId('id2'))) .toJsonString()), ])); }); group('AtFhirListen', () { test('should call subscribe with shouldDecrypt=true', () async { final Stream<AtFhirNotification> stream = AtFhirListen().build(atClient); await stream.first; // 验证subscribe方法是否被正确调用 verify(() => mockNotificationService.subscribe(shouldDecrypt: true)).called(1); }); }); }
内容的提问来源于stack exchange,提问作者Grey
相关产品推荐
相关产品推荐

