Test What You See — Testing Views
The view part of any android app comprises the display — what is visible on the screen and the user interface — what happens when the user interacts with the display.
This post is a part of this article series. However, if you want only to learn about testing views, you would be able to make sense of this post after checking the demo application. This is quite a simple and straightforward app based on MVP architecture. We will be building test cases of view part of this app.
The Robolectric
Robolectric enables testing android components locally on JVM — it doesn’t require running on emulator or real android device and therefore running Robolectric test cases are fast. This actually sounds great! How does it do that? Well, the framework actually provides a jar file — android.jar containing core level Android APIs, revised and expanded to perform quick tests on JVM. However, one must be mindful of the fact that Robolectric actually fakes Android framework to carry out its tests. Therefore, to validate real UI behavior, its better to use real Android testing frameworks like Espresso & UI Automator. With that being said, Robolectric testing framework can be extremely useful to test many UI events efficiently such as to verify if a button click starts a new activity or if background task is correctly performed by an AsyncTask or callbacks from handlers are accurately executed.
Let’s start building some Robolectric test cases for our demo app.
First and foremost, set up Robolectric to work in your project.
Add the dependency in gradle file:
testImplementation "org.robolectric:robolectric:4.3.1"As Robolectric tests run locally, they are added in src/test/(package) folder of the android project. The launcher activity of our demo app — MainActivity class- has two buttons and two textviews. The two button clicks transition to two new activities and the textviews display the welcome message text. Using Robolectric, we will verify that the correct activities are started on button clicks and the textviews are displayed correctly. Robolectric provides an API — buildActivity(class) which gives an ActivityController which gives access to perform lifecycle methods of an activity.
@Before
@Throws (Exception::class)
fun setup() {
activity = Robolectric.buildActivity(MainActivity::class.java).create().resume().get()
}The above code will run before any test method and gives a created and resumed activity. We can perform the following test to check if click on the button (‘Add new group’) starts a new activity(AddGroupActivity)
@Test
@Throws(Exception::class)
fun addButtonClickShouldStartAnotherActivity(){
val button :Button = activity.findViewById(R.id.addButton)
button.performClick()
val intent : Intent = Shadows.shadowOf(activity).peekNextStartedActivity()
assertEquals(AddGroupActivity::class.java.canonicalName,intent.component?.className)
}Robolectric transliterates all android core classes using shadow objects. These shadow objects accessed through Shadows.shadowOf() method of Shadows class. Interestingly, Shadows.shadowOf(activity) implies to look for Robolectric version of activity from android.jar and not from Activity class present in Android SDK. Next is the call to Robolectric Activity class method — peekNextStartedActivity() which returns the Intent of the next Activity to be started from activity stack without actually starting it.
Check the complete test class implementation here.
The Espresso
Espresso testing framework runs test cases on emulator or real connected device. Consequently, the execution time of these test cases is long. For instance, testing the above scenario with espresso would require first launch the activity on emulator or device and then perform button click event and then verify that new activity has started by actually starting a new activity on emulator or device. Everything that happens in Espresso is really happening with the app — No mocks, no fakes, the scenario or action is genuinely performed. Although this consumes some time but the result is strictly authentic.
Let’s build espresso test cases for one of the activities of the demo app.
To start using Espresso testing framework, add the following dependencies in build.gradle file:
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test:rules:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'Espresso tests are specific to Android. They are added in src/androidTest/(package) folder. Checkout AddMemberActivity again. The activity has various view components — TextViews, EditText, Buttons.
Get Swati Kaushik’s stories in your inbox
Join Medium for free to get updates from this writer.
Each espresso test run in isolation. Before every test the app is launched and closed after the test completes. This behavior is implemented using ActivityScenarioRule.
@get: Rule
val activityRule = ActivityScenarioRule(AddMemberActivity::class.java)For each test, ‘AddMemberActivity ‘will be directly launched on emulator or connected device.
@Test
fun checkErrorToastBlankName(){
onView(withId(R.id.addMember)).check(matches(isDisplayed()))
onView(withId(R.id.nameEdit)).perform(typeText(""), closeSoftKeyboard())
onView(withId(R.id.submitButton)).perform(click())
onView(withText("Name cannot be left blank"))
.inRoot(ToastMatcher()).check(matches(isDisplayed()))
}The above test is to check if the input field name is left blank, a toast will be displayed with text “ Name cannot be left blank”. Let us decompose the above espresso test.
onView() method is one of the entry points that Espresso provides to verify interaction with a view. This method basically finds the view in the current view hierarchy. It takes a ViewMatcher as argument to locate the view it needs to interact with. ViewMatcher is a collection of hamcrest matchers that match the views. Precisely, ViewMatcher provides different ways to find a view — using resource id or text displayed or sub text displayed etc. withId() finds view with resource id as argument. After successfully finding the view in the layout, onView() returns a ViewInteraction object which is associated with the view. ViewInteraction is a class in espresso framework which provides an interface to perform actions on views. Actions are ViewAction objects such as click(), typeText() etc — actions that are relevant to the view.
To test the toast, we need to define a custom toast matcher which extends Hamcrest’s TypeSafeMatcher class.
class ToastMatcher : TypeSafeMatcher<Root>() {
override fun describeTo(description: Description) {
description.appendText("is toast")
}
override fun matchesSafely(root: Root): Boolean {
val type = root.windowLayoutParams.get().type
if (type == WindowManager.LayoutParams.TYPE_TOAST) {
val windowToken = root.decorView.windowToken
val appToken = root.decorView.applicationWindowToken
if (windowToken === appToken) {
return true
}
}
return false
}
}TypeSafeMatcher class is an abstract class containing two methods — describeTo() and matchesSafely() to be overridden by any class that extends it. A toast window has layout parameters of type Toast — WindowManager.LayoutParams.TYPE_TOAST. With our concrete class — ToastMatcher extending TypeSafeMatcher class, we can verify if the active element on the screen is a Toast or a system dialog or an application etc.
Testing Fragments
To support performing tests on fragment, AndroidX provides a library — FragmentScenario. This library provides methods to launch fragments and transition through their states. It requires the below dependency to be added in build.gradle (app) file.
debugImplementation 'androidx.fragment:fragment-testing:$fragment_version'FragmentScenario’s method — launchFragmentInContainer() is used to launch any fragment with UI in isolation.
val scenario = launchFragmentInContainer<MyFragment>()Arguments can also be passed in this method to launch a fragment with some argument values such as list index or search string etc. For fragments with no UI, launchFragment() is called.
After launching the fragment using FragmentScenario, any UI test can be performed using Espresso in the same way as testing UI of any activity. To transition through the lifecycle states of a fragment FragmentScenario’s moveToState() method can be called to perform some specific tests.
Check out the following articles to other components of an MVP designed app.
Offscreen testing — Testing model
Test the middle man! — Testing presenter
Thanks for reading!










