Testing in Android with AndroidX update
Being an Android Developer for past 3 years, I never got a chance to write code for testing apps. Actually it’s not about chance, the environment which I am working currently never felt the need of a automation testing. Many app developers who works on a time bound client project might be in the same situation like mine. So I myself took an initiative to introduce TDD in our development realm. It seems to be very confusing in the beginning, later it felt very interesting because our app operates itself while executing UI testing !! You will definitely enjoy this if you have a curiosity level of a kid.
As the title says this article will explain the theory in detail and dig more in to code. So let’s get started.
Before talking about TDD, let’s see the types of testing generally done by developers.
There are mainly 3 categories of tests:
- Small tests:- A small test is similar to a unit test: it only interacts with the specified class. Unit test comes under this category. Applications are built of many small units. These are small, highly focused, specialized components that do one thing and do it well. Collections of these small units are assembled together so that their collaborations will satisfy our feature. This test doesn’t interact with any file system or network.
- Medium tests:- Medium tests are integration tests that sit in between small tests and large tests. They integrate several components, and they run on emulators or real devices. Integration tests are small and focused on a single UI component. Medium tests evaluate how your app coordinates multiple units, but they don’t test the full app. Examples of medium tests include service tests, integration tests, and hermetic UI tests that simulate the behavior of external dependencies. It can accesses file systems and database of the machine they’re running on.
- Large tests:- Large tests are integration and UI tests that run by completing a UI workflow. They ensure that key end-user tasks work as expected on emulators or real devices. It’s important to test common workflows and use cases that involve the complete stack, from the UI through business logic to the data layer. It can accesses external file systems, networks, etc.
In other words we can classify the test categories like below also a little bit more specifying way :
- UI Tests:
These tests interact with the UI of your app, they emulate the user behavior and assert UI results. These are the slowest and most expensive tests you can write because they require a device/emulator to run. On Android, the most commonly used tools for UI testing are Espresso and UI Automator.
- Integration Tests:
When you need to check how your code interacts with other parts of the Android framework but without the complexity of the UI. These tests don’t require a device/emulator to run. On Android, the most common tool for integration testing is Roboelectric.
- Unit Tests:
The system under test (SUT) is one class and you focus only on it. All dependencies are considered to be working correctly (and ideally have their own unit tests ), so they are mocked or stubbed.
These tests are the fastest and least expensive tests you can write because they don’t require a device/emulator to run. On Android, the most commonly used tools for unit testing are JUnit and Mockito.
They generally recommend the following split among the categories: 70 percent small, 20 percent medium, and 10 percent large.
As Per the Android Developers blog, a small test should take < 100ms, a medium test < 2s, and a large test < 120s.
Test types(based on where it runs) and location
- Local unit tests — tests which can run on the JVM.
- Instrumented tests — tests which require the Android device.
The location of your test code depends on the type of test you are writing. Android Studio provides source code directories (source sets), for the following two types of tests:
Local unit tests
These are tests that run on your machine’s local Java Virtual Machine (JVM). Use these tests to minimize execution time when your tests have no Android framework dependencies or when you can mock the Android framework dependencies. Unit tests tend to be fast because units test don’t test any Android specific dependency (Activity,SharedPreference) or mock objects are used to mimic the behavior of android framework dependencies. At runtime, these tests are executed against a modified version of
android.jar where all
final modifiers have been stripped off. This lets you use popular mocking libraries, like Mockito.
These are tests that run on a hardware device or emulator. These tests have access to
Instrumentation APIs, give you access to information such as the
Context of the app you are testing, and let you control the app under test from your test code. Use these tests when writing integration and functional UI tests to automate user interaction, or when your tests have Android dependencies that mock objects cannot satisfy. Also Use this approach to run unit tests that have complex Android dependencies that require a more robust environment, such as Robolectric. Because instrumented tests are built into an APK (separate from your app APK), they must have their own
AndroidManifest.xml file. However, Gradle automatically generates this file during the build so it is not visible in your project source set.
Instrumentation tests use a separate apk for the purpose of testing. Thus, every time a test case is run, Android Studio will first install the target apk on which the tests are conducted. After that, Android Studio will install the test apk which contains only test related code.
Android Test Support Library :- ATSL
The Android Testing Support library (ATSL) project from Google provides tooling for Android testing. For example, its (
Even though unit test is more important in Android, let’s start with UI test as it is easy to understand as a beginner. Later I will explain about the unit test types and the steps to do it.
If you run local unit tests, a special version of the android.jar (also known as the
Android mockable jar) is created by the tooling. This modified JAR file is provided to the unit test so that all fields, methods and classes are available. Any call to the Android mockable JAR results, by default, in an exception, but you can configure Android to return default values.
The library provides a JUnit 4-compatible test runner (
AndroidJUnitRunner), the Espresso test framework and the UI Automator test framework. Espresso test framework can be used to test the User Interface of your application. UI Automator allows to write cross application functional tests.
AndroidJunitRunner provides access to the instrumentation API, via the
InstrumentationRegistry.getInstrumentation(), returns the
InstrumentationRegistry.getContext(), returns the
Contextof this Instrumentation’s package.
It also gives access to the life cycle via the
UI test using Espresso
Espresso is a testing framework by Google for UI (user-interface) testing which includes every
View component like buttons, List, Fragments etc. Espresso is collection of testing APIs, specifically designed for instrumentation/UI testing. UI tests ensure that users don’t have poor interactions or encounter unexpected behaviors. The Espresso testing framework is an instrumentation-based API and works with the
AndroidJUnitRunner test runner.
A key benefit of using Espresso is that it provides automatic synchronization of test actions with the UI of the app you are testing. Espresso detects when the main thread is idle, so it is able to run your test commands at the appropriate time, improving the reliability of your tests. This capability also relieves you from having to add any timing workarounds, such as
Thread.sleep() in your test code.
Anatomy of a UI test:-
- Find a View.
- Perform an action
- Inspect the result
The same like above when using Espresso the basic flow is:
- onView(Matcher<View>) :- Find a view using some matching rules
- .perform(ViewAction) :- then perform an action on it.
- .check(ViewAssertion) :- finally verify the resulting stage.
ViewMatcher : used to locate the view in the UI hierarchy(tree structure of xml layout components) using
withText("find by text on view").
ViewActions : used to perform a specific action or group of actions in the UI views using
ViewAssertion : used to assert view’s state using
ViewInteraction.check(assertion method)where assertion methods can be
You can find the cheat sheet below
After performing an action on the View we will want to see if the view behaves as we want, This can be done using check(ViewAssertion viewAssert)
- Add Espresso dependencies in build.gradle of your app module
// To enable instrumentation testing, add the following dependency
androidTestImplementation 'androidx.test.espresso:espresso- core:3.1.0-alpha4'
Note:- If you tried to use the latest dependency versions and facing any error, please fork it to the below git link.
Turn off animations on your test device — leaving system animations turned on in the test device might cause unexpected results or may lead your test to fail. Turn off animations from Settings by opening Developer options and turning all the following options off:
- Window animation scale
- Transition animation scale
- Animator duration scale
2. Create a test class
Open the java class which you want to create corresponding test class. Press Ctrl+Shift+T. It will open a dialog saying ‘Create New Test’
Change class name if needed and press OK. Then select under which package we need to add this class. All instrumented test class goes to androidTest package.
Add the below code in test file.
- AndroidJUnitRunner :
AndroidJUnitRunneris a JUnit test runner that lets you run JUnit tests on Android devices while providing annotations to ease-up the testing procedure. For instrumentation tests, the test class must be prefixed with @RunWith(AndroidJUnit4.class).
- Annotations :
@TestAnnotation is used to mark a method for testing and methods with
@Beforeannotation are executed first to setup the environment for testing and methods with the
@Afterannotation executed at the end.
@LargeTestannotation is used to indicate that the test duration can be greater than 1 second.
- By using
ActivityTestRule, the testing framework launches the activity under test before each test method annotated with
@Testand before any method annotated with
@Before. The framework handles shutting down the activity after the test finishes and all methods annotated with
@Ruleannotation means that this is a JUnit4 test rule. JUnit4 test rules are run before and after every test method (annotated with
@BeforeClass: this indicates that the static method this annotation is applied to must be executed once and before all tests in the class. This could be used, for example, to set up a connection to a database.
@Before: indicates that the method this annotation is attached to must be executed before each test method in the class.
@Test: indicates that the method this annotation is attached to should run as a test case.
@After: indicates that the method this annotation is attached to should run after each test method.
@AfterClass: indicates that the method this annotation is attached to should run after all the test methods in the class have been run. Here, we typically close out resources that were opened in
view matchers we can use are:
withText(): returns a matcher that matches
TextViewbased on its text property value.
withHint(): returns a matcher that matches
TextViewbased on its hint property value.
withTagKey(): returns a matcher that matches
Viewbased on tag keys.
withTagValue(): returns a matcher that matches
Views based on tag property values.
withId()to look for a widget with a given id.
check()method to confirm if the underlying
Viewhas a certain state.
matches()static method returns a generic
ViewAssertionthat asserts that a view exists in the view hierarchy and is matched by the given view matcher.
isDisplayed()is a matcher that matches
Views that are currently displayed on the screen to the user.
hasFocus(): returns a matcher that matches
Views that currently have focus.
isChecked(): returns a matcher that accepts if and only if the view is a
CompoundButton(or subtype of) and is in checked state. The opposite of this method is
isSelected(): returns a matcher that matches
Views that are selected.
Some of the view actions available are:
typeText()to imitate typing text into an
clearText()to simulate clearing text in an
doubleClick()to simulate double-clicking a
longClick()to imitate long-clicking a
scrollTo()to simulate scrolling a
ScrollViewto a particular
Viewthat is visible.
swipeLeft()to simulate swiping right to left across the vertical center of a
To Run Test:
- Select the
- Right click on the file.
- Select run
I am wrapping up this part here. This article is for beginners who wants to get hands on in TDD. I will continue the remaining topic in part 2. See you soon. Clap if you liked this one.