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

Android集成测试中MainActivity提前销毁问题咨询

问题分析与解决方案

为什么MainActivity会被销毁?

你的测试类LoginIT继承了AndroidIntegrationTest,而父类中声明了mainActivityRule = activityScenarioRule<MainActivity>——这会导致测试启动时同时启动MainActivity和LoginActivity。

在Android系统中,当新的Activity(LoginActivity)启动时,后台的MainActivity会被系统标记为可销毁状态,尤其是在测试环境下,系统不会保留后台Activity的实例,所以很快就进入了DESTROYED状态。这也是你调用mainActivityRule.scenario.onActivity时抛出空指针异常的根本原因。

另外,你用while(!done)轮询等待回调的方式也很不严谨,容易导致测试超时或卡死。


解决方案

1. 移除父类中不必要的MainActivityRule

既然你的测试是针对LoginActivity的集成测试,不需要提前启动MainActivity。修改父类,把mainActivityRule移到需要它的子类中:

@RunWith(AndroidJUnit4::class)
abstract class AndroidIntegrationTest {
    @get:Rule
    val serviceRule = ServiceTestRule()
    // 移除mainActivityRule,让子类按需声明
}

2. 登录成功后再启动/等待MainActivity

登录成功后,应用应该会自动跳转到MainActivity,你可以在测试中等待这个跳转完成后再做断言,而不是依赖提前启动的已销毁实例:

class LoginIT : AndroidIntegrationTest() {
    @get:Rule
    var loginActivityRule = activityScenarioRule<LoginActivity>()

    @Test
    fun login_setsUser() {
        // 模拟登录操作
        loginActivityRule.scenario.onActivity { loginActivity ->
            loginActivity.usernameEditText.setText("Test001")
            loginActivity.passwordEditText.setText("0000")
            loginActivity.loginButton.performClick()
        }

        // 等待MainActivity启动并验证状态
        val mainScenario = ActivityScenario.launch(MainActivity::class.java)
        mainScenario.onActivity { mainActivity ->
            val userManager = mainActivity.userManager.getActiveUser()
            assertThat(userManager.creds).isInstanceOf(Valid::class.java)
            assertThat(userManager.name).isEqualTo("Test001")
        }
        mainScenario.close()
    }
}

3. 用IdlingResource替代轮询等待(推荐)

轮询等待是低效且不可靠的,你可以用Espresso的IdlingResource来监听登录异步操作的完成:

// 自定义IdlingResource监听登录状态
class LoginIdlingResource(private val loginActivity: LoginActivity) : IdlingResource {
    private var callback: IdlingResource.ResourceCallback? = null
    private var isIdle = false

    init {
        // 假设你的LoginActivity有一个登录完成的监听接口
        loginActivity.setLoginCompletedListener { succeeded ->
            isIdle = true
            callback?.onTransitionToIdle()
        }
    }

    override fun getName() = "LoginIdlingResource"
    override fun isIdleNow() = isIdle
    override fun registerIdleTransitionCallback(callback: IdlingResource.ResourceCallback?) {
        this.callback = callback
    }
}

// 测试代码中使用
@Test
fun login_setsUser() {
    var idlingResource: LoginIdlingResource? = null
    loginActivityRule.scenario.onActivity { loginActivity ->
        idlingResource = LoginIdlingResource(loginActivity)
        Espresso.registerIdlingResources(idlingResource)
        
        loginActivity.usernameEditText.setText("Test001")
        loginActivity.passwordEditText.setText("0000")
        loginActivity.loginButton.performClick()
    }

    // Espresso会自动等待登录完成,再执行后续验证
    val mainScenario = ActivityScenario.launch(MainActivity::class.java)
    mainScenario.onActivity { mainActivity ->
        val userManager = mainActivity.userManager.getActiveUser()
        assertThat(userManager.creds).isInstanceOf(Valid::class.java)
        assertThat(userManager.name).isEqualTo("Test001")
    }

    idlingResource?.let { Espresso.unregisterIdlingResources(it) }
    mainScenario.close()
}

4. 检查应用跳转逻辑

确保登录成功后,你的应用代码确实会启动MainActivity(比如通过startActivity),而不是仅仅finish LoginActivity却没有跳转目标。


内容的提问来源于stack exchange,提问作者User1291

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 14:07:52