android / android-test

An extensive framework for testing Android apps
https://android.github.io/android-test
Apache License 2.0
1.16k stars 314 forks source link

Feature request: Add launch property to ActivityScenarioRule #446

Closed bohsen closed 4 years ago

bohsen commented 5 years ago

Description

Preparing a migration to ActivityScenario and ActivityScenarioRule from ActivityTestRule we found a problem with the new ActivityScenarioRule API. When using ActivityScenarioRule there's no way to delay the launch of the activity under test and explicitly launch it. This causes problems when you want to inject i.e. an in-memory database to use in your test, as the injection hasn't finished setting up before the activity launches. This is possible with the old ActivityTestRule that takes a launch parameter in the constructor and you can explicitly launch the activity in your test and that way make sure injection of test resources has finished.

Steps to Reproduce

Create a testclass that uses ActivityScenarioRule and inject an in-memory database for use as a resource during the test. Something like this:

@LargeTest
@RunWith(AndroidJUnit4::class)
class ExampleTest {
    @get:Rule val espressoDaggerMockRule = EspressoDaggerMockRule().apply {
        provides(AppDatabase::class.java, appDatabase)
    }
    @get:Rule val activityScenarioRule = activityScenarioRule<MainActivity>()
    @get:Rule val instantExecutorRule = InstantTaskExecutorRule()

    private var testUser = randomUser
    private val appDatabase = Room.inMemoryDatabaseBuilder(
        EspressoDaggerMockRule.getApp(),
        AppDatabase::class.java
    ).allowMainThreadQueries().build()

    @Test
    fun whenFirstnameEmpty_shouldShowFirstnameEmptyError() {
        val userWithFirstnameEmpty =
            User.Builder().copyOf(testUser).withFirstName("").create()

        // val scenario = ActivityScenario.launch(MainActivity::class.java)
        // activityTestRule.launchActivity(null)

        insertNewUserAction(userWithFirstnameEmpty)

        // Confirm that correct error is shown
        onView(withId(R.id.dialog_user_firstname_layout))
            .check(
                matches(
                    hasTextInputLayoutErrorText(
                        context.getString(R.string.global_view_empty_error)
                    )
                )
            )
    }
}

The above code uses Dagger2 as Dependency Injection Framework and DaggerMock for replacing injected dependencies with test-resources.

Expected Results

The instrumentationtest launches and runs.

Actual Results

java.lang.RuntimeException: Exception while computing database live data. ... Caused by: java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.

AndroidX Test and Android OS Versions

espresso_version = '3.3.0-alpha02' androidx_test_runner_version = '1.3.0-alpha02' androidx_test_rules_version = '1.3.0-alpha02' androidx_test_monitor_version = '1.3.0-alpha02' androidx_test_orchestrator_version = '1.3.0-alpha02' androidx_test_ext_core_version = '1.2.1-alpha02' androidx_test_ext_junit_version = '1.1.2-alpha02'

Link to a public git repo demonstrating the problem:

NA

brettchabot commented 4 years ago

ActivityScenario.launch was intended for this use case. Does that not work?

bohsen commented 4 years ago

Works, but then you always have to remember to call close() at the end of each test, which can easily be forgotten. Would prefer this to be handled by using ActivityScenarioRule.

brettchabot commented 4 years ago

Acknowledged. We are looking into ways to help deal with the need to call close manually - such as a lint check.

For now, you can either add a @After method to close the scenario, or put the launch in a try-with-resources block

eric-labelle commented 4 years ago

So should I understand that the launch=false will not be added to the ActivityScenarioRule like it was on activityRule?

This is needed if the ScenarioRule is to be used. I need the activityScenarioRule to be able to set rulechains for my capturescreenshot rule in UI Tests. Not sure how else I can achieve this while launching the scenario manually. The activity is often killed before the screenshot is taken otherwise.

@JvmField
@Rule
val ruleChain: RuleChain = RuleChain
        .outerRule(activityScenarioRule)
        .around(ScreenshotTakingRule())
brettchabot commented 4 years ago

@eric-labelle can you provide more details on your example - I'm not sure what ScreenshotTakingRule refers to, and how a hypothetical launch=false feature effects when the activity is killed.

plastiv commented 4 years ago

Works, but then you always have to remember to call close() at the end of each test, which can easily be forgotten. Would prefer this to be handled by using ActivityScenarioRule.

I think you can handle it by adding couple of extra lines to the project without waiting for Google. Capture screenshot before .close() as well if needed.

class MyActivityScenarioTestRule<T : Activity> : TestRule {

    lateinit var scenario: ActivityScenario<T>

    override fun apply(base: Statement, description: Description): Statement {
        return object : Statement() {
            override fun evaluate() {
                try {
                    base.evaluate()
                } finally {
                    scenario.close()
                }
            }
        }
    }

    fun launchActivity(intent: Intent) {
        scenario = ActivityScenario.launch(intent)
    }
}
eric-labelle commented 4 years ago

@brettchabot

@eric-labelle can you provide more details on your example - I'm not sure what ScreenshotTakingRule refers to, and how a hypothetical launch=false feature effects when the activity is killed.

My problem with this new ActivityScenarioRule is that since you can't use the launch false, it will always launch it at the wrong moment so I can't use the rule if I need to wait for initialization completes. Having to use the manual activityScenario.launch also will prevent me from using the RuleChain to make my screenshot taking rule (which essentially is extending TestWatcher to take a screenshot when a test fails) dependent on the activity. I could try to manually define my own ActivityScenarioTestRule as @plastiv mentioned but it's just odd to me that ActivityTestRule was able to delay launch when we needed it but ActivityScenarioRule removed this option.

eduardbosch commented 12 months ago

Hey,

I'm curious what happened with this request.

I've always used my own rule to delay the activity launch and to ensure that the close is called.

My user case is similar to @eric-labelle, being able to delay the activity launch and control when the scenario is closed so I can use a rule to take an screenshot when a test fails.

It would be nice to see this feature in the library.

Why was never added? Was because PRs were not OK or because we don't want to support this use case with a test rule?

brettchabot commented 11 months ago

Sorry about the lack of response here. It was an intentional decision to eliminate all the configuration type parameters from ActivityTestRule in ActivityScenario. Efforts to further address 'delayed launch use case' complaints stalled because there wasn't consensus on what the right API is. There is concern around offering multiple APIs that do the same thing.

I think the prudent approach is to just create your own Rule if you need specific handling across other Rules. I'd also point out that espresso now supports taking a screenshot when any espresso call fails by default.

eduardbosch commented 11 months ago

Yes, I think it'll be the best. To create our own rules for now to delay the activity launch.

I already used the espresso automatic screenshot when a test fails, but unluckily it does not work for compose or any other failure cause. The custom rule works for any type of failure.