Hi, I'm getting into instrumentation testing of co...
# android
v
Hi, I'm getting into instrumentation testing of compose activities, and most of stuff i find on the internet shows how to test 2+2=4 or simple todo apps. Are there any good thorough resources about it?
j
What kind of things interested to test in activity? In general very same things as Espresso but nicer syntax.
v
Right now I'm trying to click on a button that starts some third party (basic views) activity, wait for my activity to be resumed, than check some text field value. Find it very hard ti implement
j
I think this https://developer.android.com/jetpack/compose/testing explains most of things well but ofc in reality there is some nice ktx things want or complex idle resources. I recommend not writing ui tests over multiple activities, better test one by one and test isolation. Like test your activity all cases work with stubbed activity intent behaviour instead of waiting for it. And use activity test api resume synthetic.
But for single Activity, do createAndroidComposeRule<YourActivity>() And then rule.activity. By default that rule waits for resume but can use any api from activity test rule. Then rule.onNodeWithText or such api identify text field. Textfield default using label as testtag in compose I think. onNodeWithTestTag("label")
You can also run multiple junit test rules simultaneously, like one for your ui and one for third party activity. But I dont recommend it, as will not sync states properly between them.
Also compose test interop with Espresso, so can mix both.
v
Thanks. Right now I'm still trying to test with two activities. But any check i try after starting a basic view activity fails with: "No compose hierarchies found in the app. Possible reasons include...."
Is the any chance that when i start the basic view activity my test is restarted? Because the crash log shows the crash occuring on the first line of the test
g
Untitled
j
Dont know what you do in code, hard to know from that. Possible to share setup in test? If you want multiple activities, depends how you launch them. Recommend stubbing it, with Espresso. To make it return immediately. Avoid multiple running across ui test. Its possible but you will have a lot of issues and some not solveable.
g
I probably started from something overly complex. This is my first test ever being written. I have MainActivity (compose), it has a button, that starts third party activity (basic views) which establish a connection with ble device. I want to test that my main activity actually connects to the device. So, basically, what i want: 1. start my MainActivity 2. click button to open third party activity 3. click connect in that activity 4. wait for my activity to resume 5. check that my "status" label changed to "Connected". I Don't want to test just the UI, i know it works. I want to run this test in repeat later on, to catch some rare issue when it fails to connect.
Maybe instrumentation tests is not for that use case.
j
Yeah I recommend instead open third party activity, stub it and fake it connecting by return activity result ok with data if needed immediately. So never need the second activity. intending(toPackage("com.android.contacts")).respondWith(result) see https://developer.android.com/training/testing/espresso/intents#stubbing
If the connection updates some state shared, mutate that in test in fake implementation.
g
Wow, that looks exactly like what i need. Thanks. :)
j
Also if I remember correct, if you launch Activity B in test from Activity A with Activity contract thing it will be paused/stopped. Once get back into A test activity contract needs to be updated as not doing that automatically.
No problem, hard find these earlier Espresso stuff not being part of compose test libraries.
g
I have a problem, the third party activity is not started with: startActivityForResult. It's started with startActivity. Any way i can stubb it in testing? Assuming I can't change the code that starts the third party activity?
j
Doesnt matter, stubbing works for both use cases. Just using intending/intend and return Result ok same as going back from previous third party activity. You can change return codes, intent data and everything. Can even verify intent extras and such.
Just write intendind/intend variant that maps to how the activity launches. Like activity class intent.
Copy code
intending(allOf(
   toPackage("com.package"),
   hasComponent(hasClassName(MyActivity.class.getName())),
   hasExtras(hasEntry(equalTo("demoName")
)).respondWith(Instrumentation.ActivityResult(Activity.RESULT_OK, Intent(). apply { data here })
Once actual code run start activity with above matching it will immediately return Result ok and resume your code with that result.
g
even if the activity started with: "startActivity" and not "startActivityForResult" ?
j
Using intend can do verify data matching before proceed to be sure :)
Yes even then.
g
Surprisingly, that didn't work 😞
I still see the actual activity started
j
Make sure using intending before your code executing the startActivity btw. And double check correct intent filter.
And using respondWith OK
g
O
I'm using
Copy code
@get:Rule
val rule = createAndroidComposeRule<MainActivity>()
How can i modify it, to launch the activity during test?
j
That one automatically launching it before each test method in junit rule.
The thing here want to make sure never actually launch any new activity, only the initial one Main activity
If not working try rule.activity.moveToState(Resume)
I dont have computer available. Typing on smartphone. Otherwise would give complete sample code.
g
If i put "intending" in my '@Before' function, it will be called before the activity is started right?
j
That you dont want. Want it start after activity created for espresso to work but before your button clicks open activity.
g
Life is pain 😉 My MainActivity, in onStart, start a service, that service is the one that triggers the third party "startActivity" call
j
Oh wait also need Intents.init() thats in past called with IntentsTestRule. Never called in compose variant. In @After also want Intebts.release
Should be able create this code in @Before sorry
v
I do have Intents.Init and release
j
Then something weird how broadcaster implemented or your activity filter for intending wrong.
Should see in logs also if intents recorded or matched btw.
Do you use hasPackage for activity? And double check that one has correct activity intent
If that for some reason still not working, I would guess implicit intents launched from broadcast maybe not supported or bug. Then maybe Robolectric could help you.
@Vladimir Vainer this hack could potentially work as well:
Copy code
fun launchNextActivity(scenario: ActivityScenario<out Activity>?): ActivityScenario<Activity>? {
        scenario?.moveToState(Lifecycle.State.CREATED)
        val intents = Intents.getIntents()
        return ActivityScenario.launch<Activity>(intents.last())
    }
g
I see my problem now. My \@Setup and \@Test functions are called after the activity is resumed. my third party activity is started in the onStart of my activity.
I need to setup intents before my activity launched
j
You can do late launch in compose activity rule I think. Like prevent it from be launched directly in @Before things in Junit rule. But yeah if next activity launches in onStart need to change or update test code move into onStart state instead of onResume.
But for sure the ordering important. Complex case this variant. Having 2 nested dependencies of activity B and Broadcast Receiver on top of Activity A. At some level almost not gives any value write tests imo. Or split them by unit test, screenshot testing, integration / feature testing. To make sure each behaviour logic tested by itself. The most annoying cant easy test push notifications and home screen widgets, but somewhat possible with uiautomator. If need test stuff outside your own code thats also an option with ui automator.
g
Thanks for all the help. Gave up on the idea for now. Will start with something simpler.
j
No worries, best of luck. Hope will be able to find a solution! The Espresso syntax should work, just not sure how your particular Activity flow works, if needs something or not.
g
Solved it! Found this example: https://android-review.googlesource.com/c/platform/frameworks/support/+/1554347/2/compose/ui[…]oidx/compose/ui/test/junit4/LateActivityLaunchTest.kt And it works. Now, my Before call, sets the intent stubbing, After call releases it. And the test just works. Tested multiple times, behaves consistently. Thanks.
🎉 1
j
Nice glad it worked out. Kind of what I referred to earlier as activity provider in compose test rule. In end same as calling Activity scenario manually. Glad stubbing thing actually worked. Felt bad lean into a path not working.
278 Views