Flutter单元测试:如何Mock第三方库返回的指定Stream值?
问题
使用mocktail进行Flutter单元测试时,需要Mock第三方库的Stream。尝试Mock AirPodsMotionProvider的listenForAirPodsMotionData方法使其返回指定值的Stream,但运行时报错:
Bad state: No method stub was called from within
when(). Was a real method called, or perhaps an extension method?
曾尝试创建MockFlutterAirpods类(继承Mock并实现FlutterAirPods)但未成功。
相关代码:
测试代码
void main() { late AirPodsMotionProvider airPodsMotionProvider; final mockDeviceMotionData = DeviceMotionDataMocks(); setUp(() { WidgetsFlutterBinding.ensureInitialized(); airPodsMotionProvider = AirPodsMotionProvider(); }); group('test AirPodsMotionProvider', () { group('test listenForAirPodsMotionData', () { test('test listen for incoming data', () async { final value = mockDeviceMotionData.mockDefaultDeviceMotionData; when( () => airPodsMotionProvider.listenForAirPodsMotionData(), ).thenAnswer((_) => Stream<DeviceMotionData>.value(value)); airPodsMotionProvider.listenForAirPodsMotionData().listen( expectAsync1( (event) { expect(event, value); }, ), ); }); }); }); }
AirPodsMotionProvider代码
class AirPodsMotionProvider { Stream<DeviceMotionData> listenForAirPodsMotionData() => FlutterAirpods.getAirPodsDeviceMotionUpdates; }
请问如何让该Stream返回指定值?
解决方案
问题核心是:你直接对普通类AirPodsMotionProvider的实例用when(),但mocktail只能拦截Mock类的方法调用,普通类的方法会直接执行,导致报错。下面提供两种可行方案:
方法一:通过抽象接口解耦(推荐)
这种方式符合依赖倒置原则,不仅能解决测试问题,还能让代码更易维护。
- 定义抽象接口,封装第三方库的能力:
abstract class AirPodsMotionSource { Stream<DeviceMotionData> get motionUpdates; }
- 实现真实的数据源,关联第三方库:
class FlutterAirPodsMotionSource implements AirPodsMotionSource { @override Stream<DeviceMotionData> get motionUpdates => FlutterAirpods.getAirPodsDeviceMotionUpdates; }
- 修改
AirPodsMotionProvider,让它依赖抽象接口:
class AirPodsMotionProvider { final AirPodsMotionSource motionSource; // 允许注入依赖,默认使用真实实现 AirPodsMotionProvider({AirPodsMotionSource? motionSource}) : motionSource = motionSource ?? FlutterAirPodsMotionSource(); Stream<DeviceMotionData> listenForAirPodsMotionData() => motionSource.motionUpdates; }
- 测试时创建Mock类并配置:
class MockAirPodsMotionSource extends Mock implements AirPodsMotionSource {} void main() { late AirPodsMotionProvider airPodsMotionProvider; late MockAirPodsMotionSource mockMotionSource; final mockDeviceMotionData = DeviceMotionDataMocks(); setUp(() { WidgetsFlutterBinding.ensureInitialized(); mockMotionSource = MockAirPodsMotionSource(); // 注入mock依赖到Provider airPodsMotionProvider = AirPodsMotionProvider(motionSource: mockMotionSource); }); group('test AirPodsMotionProvider', () { group('test listenForAirPodsMotionData', () { test('test listen for incoming data', () async { final value = mockDeviceMotionData.mockDefaultDeviceMotionData; // Mock数据源的属性返回指定Stream when(() => mockMotionSource.motionUpdates) .thenAnswer((_) => Stream<DeviceMotionData>.value(value)); airPodsMotionProvider.listenForAirPodsMotionData().listen( expectAsync1( (event) { expect(event, value); }, ), ); }); }); }); }
方法二:直接Mock第三方库静态属性
如果不想修改现有代码结构,可以尝试直接Mock第三方库的静态属性(注意:mocktail对静态成员的支持有限,部分场景可能无效):
class MockFlutterAirpods extends Mock implements FlutterAirpods {} void main() { late AirPodsMotionProvider airPodsMotionProvider; final mockDeviceMotionData = DeviceMotionDataMocks(); setUp(() { WidgetsFlutterBinding.ensureInitialized(); airPodsMotionProvider = AirPodsMotionProvider(); // 启用静态类Mock mockStatic(FlutterAirpods); // 配置静态属性返回指定Stream when(() => FlutterAirpods.getAirPodsDeviceMotionUpdates) .thenAnswer((_) => Stream<DeviceMotionData>.value(mockDeviceMotionData.mockDefaultDeviceMotionData)); }); group('test AirPodsMotionProvider', () { group('test listenForAirPodsMotionData', () { test('test listen for incoming data', () async { final value = mockDeviceMotionData.mockDefaultDeviceMotionData; airPodsMotionProvider.listenForAirPodsMotionData().listen( expectAsync1( (event) { expect(event, value); }, ), ); }); }); }); }
关键提示
- 优先选方法一:抽象解耦的方式更灵活,后续更换第三方库或扩展功能时,不需要修改测试代码。
- 方法二仅适合快速测试:如果第三方库的静态属性是
final类型,mocktail可能无法拦截,此时必须用方法一。
内容的提问来源于stack exchange,提问作者Lucas Goldner
相关产品推荐
相关产品推荐

