You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

纯Dart环境下Riverpod Stream Provider测试问题求助

问题分析与修复方案

第一个测试:Bad state: No element 错误

原因

你的AtFhirListen的build方法存在两个关键问题:

  1. 调用atClient.notificationService.subscribe().map(...)后,没有将生成的流内容输出到Provider的流中,导致整个Provider的流是空的,调用stream.first自然会报错“无元素”。
  2. 使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 21:25:00