JUnit4+Mockito测试Firebase登录出现Unfinished stubbing错误求助
问题
使用JUnit4和Mockito测试FirebaseAuthenticationDataSource类的登录功能时,运行测试触发Unfinished stubbing错误,提示存根未完成,无法定位错误位置。
错误信息
Unfinished stubbing detected here:
-> at com.indisparte.loginfeature.data.datasource.FirebaseAuthenticationDataSourceTest$signIn success - should emit Success result with user data$1.invokeSuspend(FirebaseAuthenticationDataSourceTest.kt:69)例如:可能缺少thenReturn()。
正确存根示例:
when(mock.isOk()).thenReturn(true);
when(mock.isOk()).thenThrow(exception);
doThrow(exception).when(mock).someVoidMethod();
提示:
- 缺少thenReturn()
- 尝试存根final方法,这不受支持
- 在完成thenReturn指令之前,尝试存根另一个mock的行为
测试代码
@ExperimentalCoroutinesApi class FirebaseAuthenticationDataSourceTest { @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule() @get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule() @Mock var mockAuth: FirebaseAuth = mock() @Mock lateinit var mockSharedPref: SharedPreferences @InjectMocks lateinit var dataSource: FirebaseAuthenticationDataSource @Before fun setup() { // 重复初始化,可移除 MockitoAnnotations.openMocks(this) } @Test fun `signIn success - should emit Success result with user data`() = runTest { val mockAuthResult = mock<AuthResult>() val mockFirebaseUser = mock<FirebaseUser> { on { email } doReturn "test@example.com" on { uid } doReturn "12345" on { displayName } doReturn "Test User" } // 问题出在这里:thenReturn中嵌套了存根操作 `when`(mockAuth.signInWithEmailAndPassword(any(), any())).thenReturn(mockTask(mockAuthResult)) //line 69 `when`(mockAuthResult.user).thenReturn(mockFirebaseUser) val signInFlow = dataSource.signIn("test@example.com", "password") val results = signInFlow.toList() assertEquals(2, results.size) assertEquals(Result.Loading, results[0]) assertEquals(Result.Success(User("test@example.com", "12345", "Test User")), results[1]) } private fun <T> mockTask(result: T): Task<T> { val mockTask = mock<Task<T>>() // 这里的存根操作不能嵌套在另一个thenReturn中执行 whenever(mockTask.isSuccessful).thenReturn(true) whenever(mockTask.result).thenReturn(result) return mockTask } }
待测试方法
class FirebaseAuthenticationDataSource @Inject constructor( private val auth: FirebaseAuth, private val sharedPref: SharedPreferences ) : AuthDataSource { override fun signIn(email: String, password: String): Flow<Result<User>> = flow { emit(Result.Loading) try { val result = auth.signInWithEmailAndPassword(email, password).await() emit( Result.Success( User( result.user!!.email ?: "", result.user!!.uid, result.user!!.displayName ?: "" ) ) ) } catch (e: Exception) { emit(Result.Error(e)) } } }
排查与修复
核心原因
错误提示的第三条线索已点明:在完成thenReturn指令之前,尝试存根另一个mock的行为。
具体来说,你在when(mockAuth.signInWithEmailAndPassword(...)).thenReturn(mockTask(mockAuthResult))这行代码中,thenReturn的参数是mockTask方法的返回值,而mockTask方法内部又对mockTask对象进行了存根操作(whenever(mockTask.isSuccessful)...)。Mockito不允许在存根一个方法的过程中(即when(...).thenReturn(...)链条执行期间)去存根另一个mock对象,这会导致存根流程未完成。
修复步骤
- 提前完成Task的存根:先调用
mockTask创建并完成存根,再将其传入thenReturn - 移除重复的Mock初始化:
MockitoRule已经会自动初始化mock对象,setup方法中的MockitoAnnotations.openMocks(this)属于重复操作,需要删除以避免冲突
修改后的测试代码片段
@Test fun `signIn success - should emit Success result with user data`() = runTest { val mockAuthResult = mock<AuthResult>() val mockFirebaseUser = mock<FirebaseUser> { on { email } doReturn "test@example.com" on { uid } doReturn "12345" on { displayName } doReturn "Test User" } // 提前创建并完成Task存根 val mockAuthTask = mockTask(mockAuthResult) // 再将已存根的Task传入thenReturn `when`(mockAuth.signInWithEmailAndPassword(any(), any())).thenReturn(mockAuthTask) `when`(mockAuthResult.user).thenReturn(mockFirebaseUser) val signInFlow = dataSource.signIn("test@example.com", "password") val results = signInFlow.toList() assertEquals(2, results.size) assertEquals(Result.Loading, results[0]) assertEquals(Result.Success(User("test@example.com", "12345", "Test User")), results[1]) } @Before fun setup() { // 移除重复的初始化代码 }
内容的提问来源于stack exchange,提问作者Antonio

