Espresso perform方法报错:无法在主应用线程调用
java.lang.IllegalStateException的原因及修复 问题描述
我正在编写一个Espresso仪器化测试,需实现点击按钮触发API调用、检查加载器可见性,之后终止Activity,代码如下:
val activityScenario: ActivityScenario<MainActivity> = ActivityScenario.launch(intent) activityScenario.onActivity { activity -> onView(withId(R.id.actionApproveContainer)).perform(click()) onView(withText(R.string.loading)).check(matches(isDisplayed())) onView(withText(R.string.loading)).check(doesNotExist()) // Assert that the activity is finished assertTrue(activity.isFinishing) }
运行测试用例时抛出java.lang.IllegalStateException: Method cannot be called on the main application thread (on: main)错误,请问哪里操作有误?
问题原因
activityScenario.onActivity的回调运行在主线程(UI线程)中,而Espresso的核心API(onView()、perform()、check())要求在测试线程执行,不允许直接在主线程调用这些方法,这就是触发异常的根本原因。
另外,代码逻辑还有潜在问题:API调用属于异步操作,直接连续调用check(matches(isDisplayed()))和check(doesNotExist()),可能因异步任务未完成导致测试失败——Espresso无法自动感知自定义异步操作的状态。
修复方案
1. 分离UI操作与Activity状态断言
将Espresso的UI操作移出onActivity回调,直接在测试线程执行;Activity状态的断言可单独放在onActivity中,但要确保等待异步操作完成后再执行。
2. 处理异步API调用(必要步骤)
如果API调用是异步的,需使用Espresso的IdlingResource机制让测试等待异步任务完成,避免测试超时或断言失败。可自定义IdlingResource监听API调用状态,或使用第三方库(如OkHttp IdlingResource)。
修正后的代码示例
val activityScenario: ActivityScenario<MainActivity> = ActivityScenario.launch(intent) // 直接在测试线程执行Espresso UI操作 onView(withId(R.id.actionApproveContainer)).perform(click()) // 等待加载器显示 onView(withText(R.string.loading)).check(matches(isDisplayed())) // 等待加载器消失(需先注册IdlingResource处理异步操作) onView(withText(R.string.loading)).check(doesNotExist()) // 单独断言Activity是否处于结束状态 activityScenario.onActivity { activity -> assertTrue(activity.isFinishing) } // 也可通过ActivityScenario的状态直接断言 assertEquals(Activity.State.DESTROYED, activityScenario.state)
IdlingResource集成示例(以OkHttp为例)
如果API调用使用OkHttp,可添加okhttp-idling-resource依赖,注册IdlingResource让测试等待网络请求完成:
// 初始化OkHttp客户端时创建IdlingResource val client = OkHttpClient.Builder() .addInterceptor(/* 自定义拦截器 */) .build() val idlingResource = OkHttp3IdlingResource.create("OkHttp", client) Espresso.registerIdlingResources(idlingResource) // 测试结束后注销资源 @After fun tearDown() { Espresso.unregisterIdlingResources(idlingResource) }
内容的提问来源于stack exchange,提问作者Chetan Patil

