Better tests with AndroidX’s ActivityScenario in Kotlin — Part 1
With the rise of AndroidX Test library came a unified approach for writing Robolectric and instrumentation tests. Previously, to manage Activity lifecycle we had to use an ActivityTestRule
in Android Test Support Library and an ActivityController
in Robolectric. Now, there’s a single API for that called ActivityScenario
. Also, ActivityTestRule
is deprecated now and will be removed in favor of ActivityScenario
.
In this article, I’ll show how to use this API in a number of different scenarios in Kotlin.
ActivityScenario
— the basics
To get started we need to add AndroidX Core KTX library to our Gradle dependencies e.g.
androidTestImplementation 'androidx.test:core-ktx:X.X.X'
The purpose of this API is briefly described in the official JavaDoc:
ActivityScenario
provides APIs to start and drive an Activity’s lifecycle state for testing. It works with arbitrary activities and works consistently across different versions of the Android framework.
ActivityScenario
offers a launch()
method which accepts either a class reference or an Intent used to launch that Activity. KTX version offers a better alternative in Kotlin though i.e. launchActivity()
inline function which can be used as follows:
or with a custom Intent:
Why we might need ActivityScenarioRule
ActivityScenario#launch
is very similar to ActivityTestRule#launchActivity
method. There is a catch though… When we read the documentation carefully, we can find the following text excerpt:
ActivityScenario does’t clean up device state automatically and may leave the activity keep running after the test finishes. Call
close()
in your test to clean up the state or use try-with-resources statement. This is optional but highly recommended to improve the stability of your tests.
This is something that ActivityTestRule
was doing automatically. Now, instead of using close()
or a try-with-resources statement we can also rely on ActivityScenarioRule
.
ActivityScenarioRule launches a given activity before the test starts and closes after the test.
You can access to scenario interface via
getScenario()
method. You may finish your activity manually in your test, it will not cause any problems and this rule does nothing after the test in such cases.
To use it we need to add JUnit KTX extension library e.g.
androidTestImplementation 'androidx.test.ext:junit-ktx:X.X.X'
Now, to launch an Activity with the default Intent we could use:
If our Activity actually requires some Intent extras things can get more complicated though:
What if we wanted to do some setup in our Activity before launching it?
With the current version of ActivityScenarioRule
we aren’t able to do that. ActivityScenarioRule
launches the Activity before we can get to our test method where we would actually like to do the setup e.g. set some Intent extras.
For this, we need to use ActivityScenario
directly and remember to clean up afterwards e.g. in an @After
annotated method or with the help of a try-with-resources block.
Option 1: clean up in @After
As methods annotated with @After
are executed after each test, we can use such a method to clean up the scenario:
Option 2: clean up with a try-with-resources
Try-with-resources doesn’t look that bad, however we need to wrap most of our test code in a use{}
block and, personally, I’ve never been a fan of adding that extra indentation in my code. Ideally, I would want to be able to launch my Activity in a similar fashion as I was able to do with ActivityTestRule
.
Option 3: use a custom LazyActivityScenarioRule
With this custom Rule, the previous scenario could be written as such:
Here’s the code for LazyActivityScenarioRule
:
I’ve copied the original code from ActivityScenarioRule
, Kotlinized it and added some more features to allow for optional launching of Activity on start (turned on by default).
What’s so great about it?
One of the biggest benefits of LazyActivityScenarioRule
, in my opinion, is that we can use it as a drop-in replacement for ActivityScenarioRule
e.g.
but also use it in the cases where we need to provide different Intent extras as described before. We can also use it when we need to do some setup before launching an Activity even though we’re using the default Intent e.g.
With it we have a single solution that meets all our needs and don’t need to think whether to use a try-with-resource approach in some tests and ActivityScenarioRule
in others.
What’s next?
In Part 2 I’ll demonstrate how to use LazyActivityScenarioRule
in both Robolectric and instrumentation tests with some more advanced examples. Stay tuned 🤘
Read more about the technologies we use or take an inside look at our organisation & processes. Interested in working at StepStone? Check out our careers page.