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

JUnit4+Mockito测试Firebase登录出现Unfinished stubbing错误求助

解决Mockito测试中Unfinished stubbing错误(Firebase登录场景)

问题

使用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();
提示:

  1. 缺少thenReturn()
  2. 尝试存根final方法,这不受支持
  3. 在完成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对象,这会导致存根流程未完成。

修复步骤

  1. 提前完成Task的存根:先调用mockTask创建并完成存根,再将其传入thenReturn
  2. 移除重复的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 23:12:47