Skip to main content
April 13, 2022

User Interface Tests in Kotlin | How to implement them

Looking at “Advanced Android in Kotlin” code labs series from google you can find one about Testing and Dependency Injection. It is not only a testing practice but an architecture guide for android too.

When you reach the third section you will start to write User Interface tests with Espresso. It is awesome to run them and see the app running alone and doing things. Although it is filed as an advanced code lab, the code for the test can be refactored using the Page Object Pattern

Page Object Pattern

The sense of a page object is to hide the “odd” code needed to replicate the steps needed to navigate through our app, perform actions as a user and provide a clean interface to create meaning and useful tests with.

To see this in action we will use the code from the code lab repo final solution you can find here:

https://github.com/googlecodelabs/android-testing/tree/end_codelab_3

We will work from the “end_codelab_3” branch.

Page Object in Kotlin

We will start by adding the base Kotlin class Page that will start the magic. Add this class to the “androidTest” source set:

open class Page {

companion object {

inline fun <reified T : Page> on(): T {

return Page().on()

}

}

inline fun <reified T : Page> on(): T {

val page = T::class.constructors.first().call()

page.verify()

//Thread.sleep(500) //To give time when asynchronous calls are invoked

return page

}

open fun verify(): Page {

// Each subpage should have its default assurances here

return this

}

fun back(): Page {

Espresso.pressBack()

return this

}

}

If you want to go deep on this, please take a look to Page Object Pattern in Kotlin for UI Test Automation On Android from Kate Savelova

In the end, this class is used to be able to define a fluent API that will help us to organize the code needed to perform navigation, actions, and checks throughout our app pages, views, etc. Pay special attention to the verify function. This function is used to check if the page we want has been loaded.

Let’s start by adding our first test with this page. Go to AppNavigationTest.kt file and add a new test that will add a new task to the app.

@Test

fun createNewTask()  {

// Start up Tasks screen

val activityScenario = ActivityScenario.launch(TasksActivity::class.java)

dataBindingIdlingResource.monitorActivity(activityScenario)

 

// When using ActivityScenario.launch, always call close()

activityScenario.close()

}

This is the starter code for the test that just launch the TasksActivity, for a deep understanding of this take a look at the code lab: https://developer.android.com/codelabs/advanced-android-kotlin-training-testing-survey#0

The full test code is:

@Test

fun createNewTask()  {

// Start up Tasks screen

val activityScenario = ActivityScenario.launch(TasksActivity::class.java)

dataBindingIdlingResource.monitorActivity(activityScenario)

 

val testTask= Task(“title”, “description”)

 

Page.on<TasksPage>()

.tapOnAddButton()

.on<AddTaskPage>()

.addTask(testTask)

.on<TasksPage>()

.checkAddedTask(testTask)

 

// When using ActivityScenario.launch, always call close()

activityScenario.close()

}

See the beauty of the test:

  • Create a task
  • In TasksPage, taps on Add Button
  • On AddTaskPage, add the task we created in step 1
  • Now on TasksPage, check the task has been added.

It is very meaningful, and simple. But it does not compile yet, but do not worry. Let’s fix all:

Add the gradle dependency to the app build.gradle:

  • implementation “org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion”

TasksPage and AddTaskPage classes

Add the TasksPage and AddTaskPage classes in the same package of PageObject:

class TasksPage : Page() {

override fun verify(): TasksPage {

onView(withId(R.id.tasks_container_layout))

.check(matches(isDisplayed()))

return this

}

fun tapOnAddButton(): TasksPage {

onView(withId(R.id.add_task_fab)).perform(ViewActions.click())

return this

}

 

fun tapOnEditTask(): TasksPage {

onView(withId(R.id.edit_task_fab)).perform(ViewActions.click())

return this

}

 

fun checkAddedTask(testTask: Task): TasksPage {

onView(withText(testTask.title))

return this

}

}

 

class AddTaskPage: Page() {

override fun verify(): AddTaskPage {

Espresso.onView(withId(R.id.add_task_title_edit_text))

.check(ViewAssertions.matches(isDisplayed()))

return this

}

 

fun addTask(task: Task):AddTaskPage{

onView(withId(R.id.add_task_title_edit_text))

.perform(clearText(), typeText(task.title))

onView(withId(R.id.add_task_description_edit_text))

.perform(clearText(), typeText(task.description))

onView(withId(R.id.save_task_fab)).perform(click())

return this

}

}

It contains all the Espresso code needed to do all the interactions. If we do not use this pattern, we will end up with a very long test with all those Espresso methods in only one test, which make it very hard to read and maintain.

You will end with this running app on your mobile:

 

The equivalent test without using Page Object pattern is:

@Test

fun createNewTaskWithoutPageObject(){

val activityScenario = ActivityScenario.launch(TasksActivity::class.java)

dataBindingIdlingResource.monitorActivity(activityScenario)

 

val task= Task(“title”, “description”)

 

//check tasks page open

onView(withId(R.id.tasks_container_layout))

.check(matches(isDisplayed()))

 

//tap on add button

onView(withId(R.id.add_task_fab)).perform(ViewActions.click())

 

//check task page is open

onView(withId(R.id.add_task_title_edit_text))

.check(ViewAssertions.matches(isDisplayed()))

 

//add task

onView(withId(R.id.add_task_title_edit_text))

.perform(ViewActions.clearText(), ViewActions.typeText(task.title))

onView(withId(R.id.add_task_description_edit_text))

.perform(ViewActions.clearText(), ViewActions.typeText(task.description))

onView(withId(R.id.save_task_fab)).perform(click())

 

//check task page is open

onView(withId(R.id.tasks_container_layout))

.check(matches(isDisplayed()))

 

//check added task

onView(withText(task.title))

 

// When using ActivityScenario.launch, always call close()

activityScenario.close()

}

Which test code will you prefer?

The benefits of using this pattern to build UI tests are:

  • Meaningful UI test:
    • As you can see, the UI test code is very descriptive, it is written like a novel.
  • Maintaining:
    • Whenever the UI changes, we only must change those pages that have been affected by that. But with this architecture, it is easy to find them, and it is easier to figure out why
    • Adding new tests becomes easy to avoid duplicated code.

Summary

Page Object is a great widely pattern used to perform UI tests in other platforms like java, JavaScript, c#, etc. And as you can see it is very easy once understood. We hope you find this post useful.

You can see a full version of this code on my google code lab fork.

 

Author
Juan María Laó
Software Development Engineer