Disabling animations when running Android instrumented tests

Photo by Isaac Li Shung Tan on Unsplash

Would you like to make your Android instrumented tests, e.g. espresso tests, less flaky and dramatically faster to execute? Then read on.

I will show a reliable and extensible approach to automatically disabling animations on test devices before running instrumented tests and then restoring device animation settings after the test run is over.

Overview

Approach summary:

  1. Replace a standard AndroidJUnitRunner with a custom test runner that extends from it.
  2. Add a test runner delegate that will be triggered at the beginning/end of the test run.
  3. Use Espresso's UiAutomation.executeShellCommand() in a test runner delegate to read and modify device animation settings.

Extensibility note: you can add more delegates for other functionality like automatically unlocking your device before running a test.

Advantages compared to existing approaches I found online:

Step 1: Custom Test Runner

Instrumented tests are executed by a test runner class, which is AndroidJUnitRunner by default. You can probably find this line in your build.gradle:

testInstrumentationRunner androidx.test.runner.AndroidJUnitRunner

AndroidJUnitRunner is an extensible class that offers lifecycle methods like onCreate() or finish(). We will override those methods to invoke our own code, i.e. enable/disable animations, and then configure Gradle to use our custom test runner class instead of AndroidJUnitRunner.

CustomAndroidTestRunner template

Note: If you are setting up a new project without any instrumented tests, check that you have espresso-core dependency in your build.gradle.

Replace the testInstrumentationRunner in your build.gradle with:

testInstrumentationRunner full.package.name.of.your.CustomTestRunner

Step 2: Test Runner Delegates

When writing a custom test runner, let’s follow a structured and extensible approach from the beginning. Instead of putting our own code directly into the test runner, we will put it into separate delegate classes and invoke those classes from the test runner in a unified fashion.

Let’s create a delegate interface and add a list of delegates into the test runner class:

CustomAndroidTestRunner implementation

Note that there is no way to provide constructor arguments to the test runner in Gradle. So we have to provide a delegates list as a default constructor argument.

Step 3: AnimationsTestRunnerDelegate

Now, let’s implement the first delegate that will read device animation settings before the test run, disable animations, and then restore animation settings after the test run is over.

Everything we need is an EspressoUiAutomation object that can execute shell commands on the device. We can achieve it by doing the following:

InstrumentationRegistry.getInstrumentation().uiAutomation
.executeShellCommand("%some command%")

Note: this is an equivalent of invoking “adb shell %some command%” on your laptop’s CLI.

And here are the commands we would need to read/set animations value:

"settings get global animator_duration_scale""settings put global animator_duration_scale $value""settings put global transition_animation_scale $value""settings put global window_animation_scale $value"

By the way, these are the same settings that you see in a developer options menu:

Let’s put it all together in a delegate:

AnimationsTestRunnerDelegate that enables/disables device animations

Note: I have intentionally left out exception handling for clarity of the example.

That’s it. No reflection, no magic, just invoking adb shell settings commands at the right time and place. After you add AnimationsTestRunnerDelegateto your CustomTestRunner, it will do the job whenever you run any instrumented tests from CLI or from Android Studio.

Conclusion

With that approach in place, your Android instrumented tests will run much faster (animations take time) and be more reliable. And you can add your own delegates to the test runner, e.g. to turn on the screen using a WakeLock, or to unlock the device using KeyguardManager.

Cheers!

--

--

--

Self-Employed Senior Android Dev | https://romantikonov.com/

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

A Beginner’s Guide to Setting up OpenCV Android Library on Android Studio

No more LiveData in Your Repository: There are better options.

Adding a Powerup

Unit testing on Android

Jetpack Compose Components (Part 2)

Colorful template

Hacking Android Using Ghost Framework

Server-Driven UI for Android with Jetpack Compose

Theming in Jetpack Compose for Android

Colors

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
Roman Tikonov

Roman Tikonov

Self-Employed Senior Android Dev | https://romantikonov.com/

More from Medium

Automate your Android build code

Manage back navigation from notification in android

Android app with coroutines, flow, dagger, Stateflows with MVP architecture

Why you should encapsulate all Firebase logic in replaceable classes