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

如何对基于回调的方法(如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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 04:39:04