Android 单元测试 activity 是否以超时或延迟开始后不起作用

Android unit testing if an activity starts with a timeout or after a delay is not working

我正在使用 Kotlin 编程语言开发 Android 应用程序。我正在将仪器测试添加到我的项目中。现在,我在测试 activity 是否在延迟后启动时遇到问题。我正在使用 Espresso 进行仪器测试。

这是我的activityclass

class MainActivity : AppCompatActivity() {

    companion object {
        val LAUNCH_DELAY: Long = 2000
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Handler().postDelayed({
            this.startLoginActivity()
        }, LAUNCH_DELAY)
    }

    protected fun startLoginActivity()
    {
        startActivity(Intent(this, LoginActivity::class.java))
    }
}

我试过了

@RunWith(AndroidJUnit4::class)
class MainActivityTest {
    @Rule @JvmField
    val mainActivityRule: ActivityTestRule<MainActivity> = ActivityTestRule<MainActivity>(MainActivity::class.java)

    @Test
    fun itStartsLoginActivityAfterDelay() {
        Intents.init()
        mainActivityRule.launchActivity(Intent())
        Handler().postDelayed({
            Intents.intended(hasComponent(LoginActivity::class.java.name))
            Intents.release()
        }, 2000);
    }
}

我遇到了这个错误

E/TestRunner: failed: itStartsLoginActivityAfterDelay(com.example.memento.MainActivityTest)
    ----- begin exception -----
E/TestRunner: java.lang.RuntimeException: Can't create handler inside thread Thread[Instr: com.example.memento.MockTestRunner,5,main] that has not called Looper.prepare()
        at android.os.Handler.<init>(Handler.java:205)
        at android.os.Handler.<init>(Handler.java:118)
        at com.example.memento.MainActivityTest.itStartsLoginActivityAfterDelay(MainActivityTest.kt:37)
        at java.lang.reflect.Method.invoke(Native Method)
        at org.junit.runners.model.FrameworkMethod.runReflectiveCall(FrameworkMethod.java:50)
        at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
        at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
        at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
        at androidx.test.rule.ActivityTestRule$ActivityStatement.evaluate(ActivityTestRule.java:531)
        at org.junit.rules.RunRules.evaluate(RunRules.java:20)
        at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:290)
        at org.junit.runners.ParentRunner.schedule(ParentRunner.java:71)
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
        at org.junit.runners.ParentRunner.access[=14=]0(ParentRunner.java:58)
        at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:268)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
        at androidx.test.runner.AndroidJUnit4.run(AndroidJUnit4.java:104)
        at org.junit.runners.Suite.runChild(Suite.java:128)
        at org.junit.runners.Suite.runChild(Suite.java:27)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:290)
        at org.junit.runners.ParentRunner.schedule(ParentRunner.java:71)
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
        at org.junit.runners.ParentRunner.access[=14=]0(ParentRunner.java:58)
        at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:268)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
        at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
        at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
        at androidx.test.internal.runner.TestExecutor.execute(TestExecutor.java:56)
        at androidx.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:388)
        at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2145)
    ----- end exception -----

我也试过这个

@Test
    fun itStartsLoginActivityAfterDelay() {
        Intents.init()
        mainActivityRule.launchActivity(Intent())
        Thread.sleep(2000);
        Intents.intended(hasComponent(LoginActivity::class.java.name))
        Intents.release()
    }

这次报错

E/TestRunner: failed: itStartsLoginActivityAfterDelay(com.example.memento.MainActivityTest)
E/TestRunner: ----- begin exception -----
E/TestRunner: androidx.test.espresso.base.DefaultFailureHandler$AssertionFailedWithCauseError: Wanted to match 1 intents. Actually matched 2 intents.

    IntentMatcher: has component: has component with: class name: is "com.example.memento.LoginActivity" package name: an instance of java.lang.String short class name: an instance of java.lang.String

    Matched intents:
    -Intent { cmp=com.example.memento/.LoginActivity } handling packages:[[com.example.memento]])
    -Intent { cmp=com.example.memento/.LoginActivity } handling packages:[[com.example.memento]])

    Recorded intents:
    -Intent { flg=0x10000000 cmp=com.example.memento/.MainActivity } handling packages:[[com.example.memento]])
    -Intent { cmp=com.example.memento/.LoginActivity } handling packages:[[com.example.memento]])
    -Intent { cmp=com.example.memento/.LoginActivity } handling packages:[[com.example.memento]])
        at dalvik.system.VMStack.getThreadStackTrace(Native Method)
        at java.lang.Thread.getStackTrace(Thread.java:1538)
        at androidx.test.espresso.base.DefaultFailureHandler.getUserFriendlyError(DefaultFailureHandler.java:94)
        at androidx.test.espresso.base.DefaultFailureHandler.handle(DefaultFailureHandler.java:57)
        at androidx.test.espresso.ViewInteraction.waitForAndHandleInteractionResults(ViewInteraction.java:318)
        at androidx.test.espresso.ViewInteraction.check(ViewInteraction.java:300)
        at androidx.test.espresso.intent.Intents.intended(Intents.java:189)
        at androidx.test.espresso.intent.Intents.intended(Intents.java:170)
        at com.example.memento.MainActivityTest.itStartsLoginActivityAfterDelay(MainActivityTest.kt:47)
        at java.lang.reflect.Method.invoke(Native Method)
        at org.junit.runners.model.FrameworkMethod.runReflectiveCall(FrameworkMethod.java:50)
        at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
        at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
        at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
        at androidx.test.internal.runner.junit4.statement.RunBefores.evaluate(RunBefores.java:80)
        at androidx.test.rule.ActivityTestRule$ActivityStatement.evaluate(ActivityTestRule.java:531)
        at org.junit.rules.RunRules.evaluate(RunRules.java:20)
        at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:290)
        at org.junit.runners.ParentRunner.schedule(ParentRunner.java:71)
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
        at org.junit.runners.ParentRunner.access[=16=]0(ParentRunner.java:58)
        at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:268)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
        at androidx.test.runner.AndroidJUnit4.run(AndroidJUnit4.java:104)
        at org.junit.runners.Suite.runChild(Suite.java:128)
        at org.junit.runners.Suite.runChild(Suite.java:27)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:290)
        at org.junit.runners.ParentRunner.schedule(ParentRunner.java:71)
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
        at org.junit.runners.ParentRunner.access[=16=]0(ParentRunner.java:58)
        at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:268)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
        at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
        at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
        at androidx.test.internal.runner.TestExecutor.execute(TestExecutor.java:56)
        at androidx.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:388)
        at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2145)
     Caused by: junit.framework.AssertionFailedError: Wanted to match 1 intents. Actually matched 2 intents.

    IntentMatcher: has component: has component with: class name: is "com.example.memento.LoginActivity" package name: an instance of java.lang.String short class nam
    ----- end exception -----
I/TestRunner: finished: itStartsLoginActivityAfterDelay(com.example.memento.MainActivityTest)
I/MonitoringInstr: Activities that are still in CREATED to STOPPED: 4
    Finishing activity: com.example.memento.LoginActivity@97c0733
I/MonitoringInstr: Finishing activity: com.example.memento.LoginActivity@65a941b
I/MonitoringInstr: Finishing activity: com.example.memento.MainActivity@39cc00e

androidx.test.espresso.base.DefaultFailureHandler$AssertionFailedWithCauseError: Wanted to match 1 intents. Actually matched 2 intents.

IntentMatcher: has component: has component with: class name: is "com.example.memento.LoginActivity" package name: an instance of java.lang.String short class name: an instance of java.lang.String

Matched intents:
-Intent { cmp=com.example.memento/.LoginActivity } handling packages:[[com.example.memento]])
-Intent { cmp=com.example.memento/.LoginActivity } handling packages:[[com.example.memento]])

Recorded intents:
-Intent { flg=0x10000000 cmp=com.example.memento/.MainActivity } handling packages:[[com.example.memento]])
-Intent { cmp=com.example.memento/.LoginActivity } handling packages:[[com.example.memento]])
-Intent { cmp=com.example.memento/.LoginActivity } handling packages:[[com.example.memento]])
at dalvik.system.VMStack.getThreadStackTrace(Native Method)
at java.lang.Thread.getStackTrace(Thread.java:1538)
at androidx.test.espresso.base.DefaultFailureHandler.getUserFriendlyError(DefaultFailureHandler.java:94)
at androidx.test.espresso.base.DefaultFailureHandler.handle(DefaultFailureHandler.java:57)
at androidx.test.espresso.ViewInteraction.waitForAndHandleInteractionResults(ViewInteraction.java:318)
at androidx.test.espresso.ViewInteraction.check(ViewInteraction.java:300)
at androidx.test.espresso.intent.Intents.intended(Intents.java:189)
at androidx.test.espresso.intent.Intents.intended(Intents.java:170)
at com.example.memento.MainActivityTest.itStartsLoginActivityAfterDelay(MainActivityTest.kt:47)
at java.lang.reflect.Method.invoke(Native Method)
at org.junit.runners.model.FrameworkMethod.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at androidx.test.internal.runner.junit4.statement.RunBefores.evaluate(RunBefores.java:80)
at androidx.test.rule.ActivityTestRule$ActivityStatement.evaluate(ActivityTestRule.java:531)
at org.junit.rules.RunRules.evaluate(RunRules.java:20)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access[=16=]0(ParentRunner.java:58)
at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at androidx.test.runner.AndroidJUnit4.run(AndroidJUnit4.java:104)
at org.junit.runners.Suite.runChild(Suite.java:128)
at org.junit.runners.Suite.runChild(Suite.java:27)
at org.junit.runners.ParentRunner.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access[=16=]0(ParentRunner.java:58)
at org.junit.runners.ParentRunner.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
at androidx.test.internal.runner.TestExecutor.execute(TestExecutor.java:56)
at androidx.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:388)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2145)
Caused by: junit.framework.AssertionFailedError: Wanted to match 1 intents. Actually matched 2 intents.

IntentMatcher: has component: has component with: class name: is "com.example.memento.LoginActivity" package name: an instance of java.lang.String short class name: an instance of java.lang.String

Matched intents:
-Intent { cmp=com.example.memento/.LoginActivity } handling packages:[[com.example.memento]])
-Intent { cmp=com.example.memento/.LoginActivity } handling packages:[[com.example.memento]])

Recorded intents:
-Intent { flg=0x10000000 cmp=com.example.memento/.MainActivity } handling packages:[[com.example.memento]])
-Intent { cmp=com.example.memento/.LoginActivity } handling packages:[[com.example.memento]])
-Intent { cmp=com.example.memento/.LoginActivity } handling packages:[[com.example.memento]])
at junit.framework.Assert.fail(Assert.java:50)
at androidx.test.espresso.intent.VerificationModes$Times.verify(VerificationModes.java:80)
at androidx.test.espresso.intent.Intents.internalIntended(Intents.java:346)
at androidx.test.espresso.intent.Intents.check(Intents.java:193)
at androidx.test.espresso.ViewInteraction$SingleExecutionViewAssertion.check(ViewInteraction.java:419)
at androidx.test.espresso.ViewInteraction.call(ViewInteraction.java:282)
at androidx.test.espresso.ViewInteraction.call(ViewInteraction.java:268)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

这也不行。我该如何测试?

问题是您有多个匹配目标意图:Wanted to match 1 intents. Actually matched 2 intents 你可以做的是像这样修改你的匹配器代码:

intended(hasComponent(LoginActivity::class.java.name), Intents.times(2))

更新:

实际问题出在你的测试中。

你在你的测试方法中调用 mainActivityRule.launchActivity(Intent()) 并且该行启动另一个 MainAcivity 为你启动另一个 LoginActivity (这就是你的意图堆栈中有两个的原因)。

您不必从测试开始 activity,因为您在 class 中定义的 mainActivityRule: ActivityTestRule<MainActivity> 会为每个测试方法执行此操作。

这应该有效:

@Test
fun itStartsLoginActivityAfterDelay() {
        Intents.init()
        Thread.sleep(2000);
        Intents.intended(hasComponent(LoginActivity::class.java.name))
        Intents.release()
}