Image for post
Image for post
Photo by monicore from Pexels

Stepping into Activity tests with ActivityScenarios

Roberto Orgiu
Mar 12, 2019 · 5 min read

Not so long ago the testing team at Google introduced the Android X Test libraries, amongst which we can find , a new way to test and code related to them. These APIs made it to stable version a few weeks back and are totally safe for use on daily basis.

The main idea of this tool is to drive the state of the so that we can simulate the app passing through the , , and the other lifecycle methods; which helps developers test the flow between these states.

A very peculiar aspect of and other APIs from the same suite is the possibility of running tests on JVM (using Robolectric) or on a device. The the only caveat of this approach is the need to move all the needed test files in the or folder, based on where we want them to run.

Running tests on the JVM using Robolectric will improve massively the execution time. When we instead run tests on a device or emulator, the is launched on the device, making the test more coherent with the real life scenarios.

Note: for the purpose of this post, it does not really matter where the test is run. We will not show how to include the libraries for running the tests in a specific environment, which can instead be found here.

The main difference between and is the control we have over the of the target can drive each state of the component with ease, and even decide to restart it and test what happens in that situation.

Introducing the scenario

Imagine we have a -aware component which is part of splash screen. After a timeout, this component would redirect the user to the login screen if they are not logged in, or to the home screen of the app instead.

We use the to stop all the actions we might take if the user decides to exit the app before the countdown is completed. Since we use this API, we need a in which we can add the component as observer, and an is the easiest way to achieve this.

Component code

The component itself is pretty small, as we can appreciate from its source code:

In the constructor of the , we pass the target , the and a duration time in milliseconds. We then get the from the and add our component as observer.

When we reach the state of the , we use a coroutine to spawn an asynchronous operation that suspends for the given time and then forwards the user to the next step, closing the right after that step.

In case we reach the before the countdown is over, we cancel it and the user is not forwarded to the next step.

The is not really important for the test we want to write, and the only thing that it should matter is that it works in these way:

  • it loads the token, saved in the for convenience;
  • if such token is not available, it will forward the users to the login screen;
  • if it is instead available, it will open the home screen of the app.

Writing a test case

Now, we know how we expect this component to work, and we want to write tests around it, so we can make sure it works as expected.

As an example, we will write a test that will check the non-logged in scenario, so we expect the login screen to be invoked:

The first thing we do is invoking from the Espresso Intent library. In this way, we can capture any and check their content later on.

We then create the of a test , created to be able to drive the as it was real.

At this point, such should be in the state, and we invoke a lambda on its main thread, creating the objects we need, included the , using the itself, which is passed as the parameter to the lambda.

Note: we made sure to delete the token, so we would be in the situation in which the login was required.

The next step is to move the activity to the state, so that our coroutine would run and launch the new screen. We set the waiting time for the test to 0, to avoid any delay or flakiness due to timing.

Once our asynchronous code was run, we use yet another function from the Espresso Intent library, that is a way to understand what it the content of the , more or less like works with . In this case, we check if the was meant to launch the login screen.

Last but not least, we need to invoke to stop recording the s, and on our , so that we free all the resources and we bring our tests to their initial state. Without this steps, our test cases would be in an unexpected (and potentially not replicable) state, which goes against the idea of having stable test suites.

Note: the complete test suite for the component can be found here.

Why should we use it?

Writing small and fast unit tests is definitely the easiest and most reliable way of verifying the code we write, but, as we built a feature, or a screen, we need to verify how the small pieces fit together and within the Android system. Most of the times, we can use integration tests to make sure the different units fit well together, but at some point we find ourselves in a situation where we need to make sure that all the code we wrote works as expected with the platform. In this scenario, being able to drive the of each component and verifying step by step, is definitely one of the best and least painful way to write a test suite, and this is where becomes an essential tool.

Special thanks to my friends Corey, Daniele, Fabio, and Walter for proofreading this blogpost.

Google Developers Experts

Experts on various Google products talking tech.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store