如何在Dart中测试StreamSubscription并创建事件?如何为抽象Uploader类编写单元测试?
测试抽象Uploader类及StreamSubscription的Dart单元测试指南
嘿,我来帮你搞定这个Uploader类的单元测试问题,同时也会把Dart里测试StreamSubscription和模拟事件的方法讲清楚~
一、整体测试思路
因为Uploader是抽象类,咱们的核心测试目标是它的实现类FirebaseUploader的subscribe方法。测试的时候一定要mock掉Firebase的依赖(Auth、DatabaseReference),别真的去调用线上服务,不然测试既慢又不稳定。咱们要验证的是:方法有没有正确获取匿名用户ID、有没有用正确的参数创建DatabaseReference、有没有正确订阅流并触发回调。
二、准备测试依赖
首先得在pubspec.yaml里加好测试相关的包,推荐用mocktail来做mock,比老的mockito更简洁:
dev_dependencies: test: ^1.24.0 mocktail: ^1.0.0
三、Mock Firebase依赖
咱们需要把FirebaseAuth、DatabaseReference这些依赖都mock出来,这样测试的时候就不会真的和Firebase交互了:
import 'package:mocktail/mocktail.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_database/firebase_database.dart'; // Mock FirebaseAuth class MockFirebaseAuth extends Mock implements FirebaseAuth {} // Mock UserCredential(匿名登录返回的对象) class MockUserCredential extends Mock implements UserCredential {} // Mock DatabaseReference class MockDatabaseReference extends Mock implements DatabaseReference {} // Mock StreamSubscription(用来验证订阅行为) class MockStreamSubscription extends Mock implements StreamSubscription {}
四、编写FirebaseUploader的单元测试
接下来写具体的测试用例,一步步验证subscribe的逻辑:
import 'package:test/test.dart'; import 'package:your_app/path/to/uploader.dart'; void main() { late MockFirebaseAuth mockAuth; late MockDatabaseReference mockDbRef; late FirebaseUploader uploader; setUp(() { // 每次测试前初始化mock实例 mockAuth = MockFirebaseAuth(); mockDbRef = MockDatabaseReference(); // 这里假设你的FirebaseUploader支持依赖注入,把mock的auth和db传进去 uploader = FirebaseUploader(auth: mockAuth, database: mockDbRef); }); test('subscribe方法应正确完成匿名登录并订阅数据库流', () async { // 1. 准备测试数据和mock行为 const testFilename = 'vacation-photo.jpg'; const testUserId = 'anonymous-12345'; final mockUserCredential = MockUserCredential(); // 模拟匿名登录返回指定的用户ID when(() => mockAuth.signInAnonymously()).thenAnswer((_) async => mockUserCredential); when(() => mockUserCredential.user?.uid).thenReturn(testUserId); // 模拟DatabaseReference返回一个包含测试事件的流 final testEvent = Event('upload_progress', data: 70); final mockStream = Stream<Event>.fromIterable([testEvent]); when(() => mockDbRef.onValue).thenReturn(mockStream); // 模拟流订阅返回mock的StreamSubscription final mockSubscription = MockStreamSubscription(); when(() => mockStream.listen(any())).thenReturn(mockSubscription); // 2. 执行测试方法,同时捕获回调的事件 late Event capturedEvent; final subscription = await uploader.subscribe(testFilename, (event) { capturedEvent = event; }); // 3. 验证所有预期行为是否发生 // 验证是否调用了匿名登录 verify(() => mockAuth.signInAnonymously()).called(1); // 验证是否用正确的userId和filename创建了DatabaseReference verify(() => uploader._databaseReference(testUserId, testFilename)).called(1); // 验证是否订阅了数据库的流 verify(() => mockDbRef.onValue.listen(any())).called(1); // 验证返回的订阅对象是我们mock的实例 expect(subscription, equals(mockSubscription)); // 验证回调确实收到了测试事件 expect(capturedEvent, equals(testEvent)); }); }
五、测试StreamSubscription和模拟事件的核心技巧
上面的测试已经用到了这些技巧,这里单独拆解一下,方便你理解:
1. 创建测试用的Stream
有两种常用方式:
- 快速创建包含固定事件的流:用
Stream.fromIterable,适合简单场景
final testStream = Stream<Event>.fromIterable([ Event('progress', data: 50), Event('complete', data: 'success'), ]);
- 手动控制事件发送:用
StreamController,适合需要动态触发事件的场景
final controller = StreamController<Event>(); // 发送一个进度事件 controller.add(Event('progress', data: 75)); // 发送一个完成事件 controller.add(Event('complete')); // 关闭流 controller.close();
2. 验证StreamSubscription的行为
- 验证
listen是否被调用:用mocktail的verify方法,确认流的订阅逻辑被执行 - 验证回调是否收到正确事件:像上面那样在测试中捕获回调的参数,然后断言是否符合预期
- 验证取消订阅:如果你的逻辑里有取消订阅的操作,可以模拟
cancel方法并验证调用:
when(() => mockSubscription.cancel()).thenAnswer((_) async {}); await subscription.cancel(); verify(() => mockSubscription.cancel()).called(1);
3. 测试错误场景
如果要测试流抛出错误的情况,可以创建错误流:
final errorStream = Stream<Event>.error(Exception('Upload failed')); when(() => mockDbRef.onValue).thenReturn(errorStream); // 测试错误回调(如果你的subscribe方法支持的话) bool errorWasTriggered = false; await uploader.subscribe(testFilename, (_) {}, onError: (error) { errorWasTriggered = true; }); expect(errorWasTriggered, isTrue);
六、抽象类的契约测试(可选)
如果你想确保所有Uploader的实现都遵守抽象类的规则,可以写一个通用测试套件,这样以后加新的实现(比如本地文件上传的LocalUploader)可以直接复用:
// 通用契约测试 void testUploaderContract(Uploader Function() createUploader) { test('subscribe应返回非空的StreamSubscription', () async { final uploader = createUploader(); final subscription = await uploader.subscribe('test.txt', (_) {}); expect(subscription, isNotNull); }); } // 在FirebaseUploader的测试中调用 void main() { testUploaderContract(() => FirebaseUploader(auth: MockFirebaseAuth(), database: MockDatabaseReference())); // 其他测试用例... }
这样不管以后加多少个Uploader实现,都能快速验证它们符合抽象类的要求。
内容的提问来源于stack exchange,提问作者bizz84
相关产品推荐
相关产品推荐

