如何测试应该使 FirebaseAuth.instance.userChanges() 调用其侦听器的 FirebaseAuth.instance.signOut() 的效果?

How to test the effect of FirebaseAuth.instance.signOut() that should make FirebaseAuth.instance.userChanges() invoke its listener?

我正在尝试在 Get to know Firebase for Flutter 代码实验室中编写 ApplicationState 的代码,同时练习测试驱动开发。 codelab中的方法signOut应该是这样的:

  void signOut() {
    // The question is about how to test the effect of this invocation 
    FirebaseAuth.instance.signOut();
  }

如果我理解正确,FirebaseAuth.instance.signOut() 应该使 FirebaseAuth.instance.userChanges() 使用包含 null UserStream<User?> 调用其侦听器。所以调用 FirebaseAuth.instance.signOut() 的效果并不直接。我不知道如何嘲笑这个。根据测试驱动开发,我永远不应该编写代码,除非我这样做是为了让(失败的测试)通过。 问题是如何编写强制我编写以下代码的失败测试:

  Future<void> init() async {
    await Firebase.initializeApp(
      options: DefaultFirebaseOptions.currentPlatform,
    );

    FirebaseAuth.instance.userChanges().listen((user) {
      if (user != null) {
        _loginState = ApplicationLoginState.loggedIn;
      }
////////////////////////////////////////////////////////////
      else {
        _loginState = ApplicationLoginState.loggedOut;  ////
      }
////////////////////////////////////////////////////////////
      notifyListeners();
    });
  }

我可以这样模拟 FirebaseAuth.instance.signOut():

   // firebaseAuth is a mocked object
   when(firebaseAuth.signOut())
        .thenAnswer((realInvocation) => Completer<void>().future);
    // sut (system under test)
    sut.signOut();
    verify(firebaseAuth.signOut()).called(1);

这迫使我调用 FirebaseAuth.instance.signOut()。但这并不强迫我写上述代码让它通过。

我测试这段代码:

Future<void> init() async {
    await Firebase.initializeApp(
      options: DefaultFirebaseOptions.currentPlatform,
    );

    FirebaseAuth.instance.userChanges().listen((user) {
/////////////////////////////////////////////////////////////
      if (user != null) {
        _loginState = ApplicationLoginState.loggedIn;    ////
      }
/////////////////////////////////////////////////////////////
      else {
        _loginState = ApplicationLoginState.loggedOut;
      }
      notifyListeners();
    });
  }

通过嘲讽FirebaseAuth.instance.signInWithEmailAndPassword():

    final userCredential = MockUserCredential();
    // firebaseAuth is a mocked object
    when(firebaseAuth.signInWithEmailAndPassword(
            email: validEmail, password: password))
        .thenAnswer((realInvocation) => Future.value(userCredential));
    // sut (system under test)
    await sut.signInWithEmailAndPassword(
        validEmail, password, firebaseAuthExceptionCallback);
    // This is the direct effect on my class, that will happen by the aforementioned code
    expect(sut.loginState, ApplicationLoginState.loggedIn);

请耐心等待我。我是测试和测试驱动开发的新手。

这是我所做的。 我没有像代码实验室那样在 init() 方法中调用一次 FirebaseAuth.instance.userChanges().listen(),而是调用了两次,一次是在 signInWithEmailAndPassword() 方法上,一次是在 signOut() 方法上。

  Future<void> signInWithEmailAndPassword(String email, String password,
      void Function(FirebaseAuthException exception) errorCallback) async {
///////////////////////////////////////////////////////////////////
    firebaseAuth.userChanges().listen(_whenNotNullUser);       ////
///////////////////////////////////////////////////////////////////

    try {
      await firebaseAuth.signInWithEmailAndPassword(
          email: email, password: password);
    } on FirebaseAuthException catch (exception) {
      errorCallback(exception);
    }
  }

  Future<void> signOut() async {
///////////////////////////////////////////////////////////////////
    firebaseAuth.userChanges().listen(_whenNullUser);          ////
///////////////////////////////////////////////////////////////////
    await firebaseAuth.signOut();
  }

  void _whenNullUser(User? user) {
    if (user == null) {
      _loginState = ApplicationLoginState.loggedOut;
      notifyListeners();
    }
  }

  void _whenNotNullUser(User? user) {
    if (user != null) {
      _loginState = ApplicationLoginState.loggedIn;
      notifyListeners();
    }
  }

这是我的测试:

test("""
        $given $workingWithApplicationState
        $wheN Calling signInWithEmailAndPassword()
          $and Calling loginState returns ApplicationLoginState.loggedIn
          $and Calling signOut()
          $and Calling loginState returns ApplicationLoginState.loggedOut
          $and Calling signInWithEmailAndPassword()
        $then Calling loginState should return ApplicationLogginState.loggedIn
          $and $notifyListenersCalled
""", () async {
    when(firebaseAuth.signInWithEmailAndPassword(
            email: validEmail, password: password))
        .thenAnswer((realInvocation) => Future.value(userCredential));
    await sut.signInWithEmailAndPassword(
        validEmail, password, firebaseAuthExceptionCallback);
    expect(sut.loginState, ApplicationLoginState.loggedIn);
    reset(notifyListenerCall);
    prepareUserChangesForTest(nullUser);
    await sut.signOut();
    verify(firebaseAuth.signOut()).called(1);
    expect(sut.loginState, ApplicationLoginState.loggedOut);
    reset(notifyListenerCall);
    prepareUserChangesForTest(notNullUser);
    when(firebaseAuth.signInWithEmailAndPassword(
            email: validEmail, password: password))
        .thenAnswer((realInvocation) => Future.value(userCredential));
    await sut.signInWithEmailAndPassword(
        validEmail, password, firebaseAuthExceptionCallback);
    expect(sut.loginState, ApplicationLoginState.loggedIn);
    verify(notifyListenerCall()).called(1);
  });

现在我不得不在 _whenNullUser() 和 _whenNotNullUser() 方法中编写登录以通过我的测试。