如何对基于回调的方法(如Firebase的verifyPhoneNumber)进行单元测试
测试实现方案
第一步:调整类的依赖注入逻辑
先修改AuthenticationService的构造逻辑,将_firebaseAuth改为可注入的参数,不要内部硬编码初始化,这样测试时才能传入Mock实例:
class AuthenticationService { final FirebaseAuth _firebaseAuth; // 正式环境默认用官方实例,测试时传入Mock实例 AuthenticationService({FirebaseAuth? firebaseAuth}) : _firebaseAuth = firebaseAuth ?? FirebaseAuth.instance; // 原有逻辑保持不变 Future<void> startAuthFlow(String phoneNumber) async { await _firebaseAuth.verifyPhoneNumber( phoneNumber: phoneNumber, verificationCompleted: _onVerificationCompleted, verificationFailed: _onVerificationFailed, codeSent: _onCodeSent, codeAutoRetrievalTimeout: _onCodeAutoRetrievalTimeout, ); } // 四个私有回调保持原有实现即可 void _onVerificationCompleted(PhoneAuthCredential credential) {/*原有逻辑*/} void _onVerificationFailed(FirebaseAuthException e) {/*原有逻辑*/} void _onCodeSent(String verificationId, int? resendToken) {/*原有逻辑*/} void _onCodeAutoRetrievalTimeout(String verificationId) {/*原有逻辑*/} }
第二步:编写单元测试用例
使用Mockito或者现成的firebase_auth_mocks包构造Mock的FirebaseAuth实例,核心逻辑是捕获verifyPhoneNumber接收到的回调参数,主动触发回调验证内部逻辑,不需要把私有回调单独暴露。
测试代码示例如下:
import 'package:mockito/mockito.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:test/test.dart'; // Mock FirebaseAuth 类,也可以直接用firebase_auth_mocks包的现成实现 class MockFirebaseAuth extends Mock implements FirebaseAuth {} void main() { late MockFirebaseAuth mockFirebaseAuth; late AuthenticationService authService; const testPhoneNumber = '+1234567890'; setUp(() { mockFirebaseAuth = MockFirebaseAuth(); authService = AuthenticationService(firebaseAuth: mockFirebaseAuth); }); // 测试verificationCompleted回调逻辑 test('验证完成回调逻辑正常执行', () async { final testCredential = PhoneAuthProvider.credential( verificationId: 'test_vid', smsCode: '123456' ); // 当verifyPhoneNumber被调用时,捕获传入的回调并主动触发 when(mockFirebaseAuth.verifyPhoneNumber( phoneNumber: testPhoneNumber, verificationCompleted: anyNamed('verificationCompleted'), verificationFailed: anyNamed('verificationFailed'), codeSent: anyNamed('codeSent'), codeAutoRetrievalTimeout: anyNamed('codeAutoRetrievalTimeout'), )).thenAnswer((invocation) async { final callback = invocation.namedArguments[#verificationCompleted] as Function(PhoneAuthCredential); callback(testCredential); }); await authService.startAuthFlow(testPhoneNumber); // 验证回调逻辑的输出,比如回调内调用了signInWithCredential就验证该方法被调用 verify(mockFirebaseAuth.signInWithCredential(testCredential)).called(1); }); // 其余三个回调的测试逻辑完全一致,仅需要捕获对应参数触发即可 test('验证码发送回调逻辑正常执行', () async { const testVid = 'test_vid'; const testResendToken = 98765; when(mockFirebaseAuth.verifyPhoneNumber( phoneNumber: testPhoneNumber, verificationCompleted: anyNamed('verificationCompleted'), verificationFailed: anyNamed('verificationFailed'), codeSent: anyNamed('codeSent'), codeAutoRetrievalTimeout: anyNamed('codeAutoRetrievalTimeout'), )).thenAnswer((invocation) async { final callback = invocation.namedArguments[#codeSent] as Function(String, int?); callback(testVid, testResendToken); }); await authService.startAuthFlow(testPhoneNumber); // 比如回调内会把verificationId存入状态,就验证状态是否符合预期 // expect(authService.currentVerificationId, testVid); }); }
补充说明
- 私有回调不需要对外暴露,只需要验证回调触发后的可观测行为即可:比如状态变化、依赖方法调用、抛出的异常等
- Firebase Auth SDK本身的功能不需要在单元测试里验证,这部分可以归入集成测试范畴
内容的提问来源于stack exchange,提问作者pitazzo
相关产品推荐
相关产品推荐

