Get started with Espresso & Kotlin!

Luis Marchani
Globant
Published in
6 min readDec 13, 2022

Start a UI testing project from scratch using those two tools

This article provides information to start a UI testing project from scratch based on Kotlin and Espresso. In the following example, we assume the app has been developed in Java and needs a specific configuration to develop and execute tests in Kotlin.

Espresso is a native framework for UI automated testing. It comes with the Android SDK and presents many features that make this tool more reliable when executing UI tests against native apps.

Espresso supports two languages for the moment: Java and Kotlin. This time we will go through everything needed to set up the basics for executing test cases under Kotlin configuration.

The following image represents what we commonly get at the time of building an Android application:

Folders Structure

This example is based on an Android application created in Java using Kotlin for testing.

The package com.example.intend (androidTest) will hold the UI tests performed against the application and com.example.intend (test) holds the unit tests.

Writing the first test

We will create our first test in Kotlin and locate it in com.example.intend (androidTest).

Kotlin Class Creation

Let’s call the class: KotlinExample. Once we create that class, we should create our first function and declare the kind of execution we’ll be performing. To do this, we will use Junit and its decorator @RunWith() , which will allow us to define the runner for this class.

@RunWith(AndroidJUnit4::class)
class KotlinExample {

@Test
fun modifyText() {
}
}

Right now, the test function seems ready to be executed. Probably you won’t get any IDE compiling warning, but at the time of executing this function, you’ll be getting the following error:

Didn't find class "com.example.intend.KotlinExample" on path: DexPathList…….

This is expected since we have yet to add our Kotlin dependencies. First, we need to add a Kotlin plugin for Gradle to our build.gradle (Project: <Project Name>) file as follows:

buildscript {
ext.kotlinVersion = "1.7.10"
ext.agpVersion = "7.3.1"
repositories {
// Insert local test repo here
google()
mavenCentral()
}

dependencies {
classpath "com.android.tools.build:gradle:$agpVersion"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
// NOTE: Do not place your application dependencies here;
// they belong in the individual module build.gradle files
}
}

Add the following two plugins in the build.gradle (Module: <Project name>.app) file:

import com.android.build.api.dsl.ManagedVirtualDevice

apply plugin: 'com.android.application'
//Plugins for handling kotlin code
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

task wrapper(type: Wrapper){
gradleVersion = '7.5.1'
}



android {
compileSdkVersion 33
buildToolsVersion rootProject.buildToolsVersion
defaultConfig {
applicationId "com.example.intend.BasicSample"
minSdkVersion 14
targetSdkVersion 33yes.
versionCode 1
versionName "1.0"
multiDexEnabled true
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

Make sure the following dependencies are already added in the same file:

dependencies {
androidTestImplementation 'androidx.test.ext:junit:1.1.4'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
}

Now you should be able to execute your test function (Right click on the function name and click run).

Let’s continue writing our first scenario. Our application is a simple app that allows us to write a text and change the title on the same page or place it on a second screen:

Emulator View

Element Inspection

For this example, we are going to rely on Android studio’s layout inspector, which you can access via Tools->Layout Inspector and select the app Id you want to inspect.

So we have a couple of sections in the inspector to see the components’ tree and the details about the components:

Layout Inspector Elements Tree
Layout Inspector Element Details

It’s a very easy-to-use inspector and shows the basics for this example. As you can see, the id for the Input in the first screen of the app under test is editTextUserInput. So we are going to use that to be able to change the value for the input through our test.

First, we need to add a rule to our tests to launch the app and close it after every test. To do that, we need to add the following dependencies to our build.gradle(Module: <Project name>) file.

// Testing-only dependencies
androidTestImplementation 'androidx.test.ext:junit:'
+ rootProject.extJUnitVersion
androidTestImplementation 'androidx.test.ext:junit-ktx:'
+ rootProject.extJUnitVersion

And then add the rule to the test class:

@RunWith(AndroidJUnit4::class)
class KotlinExample {

@get:Rule
var activityScenarioRule = activityScenarioRule<MainActivity>()

@Test
fun modifyText() {
}
}

Then we need to start dealing with the app objects. In this case, we’ll be adding certain text to the input component: editTextUserInput, then clicking on the button to change the text in the main screen, and finally verifying the text was changed:

companion object {
val STRING_TO_BE_TYPED = "Espresso"
}

@Test
fun modifyText() {

onView(withId(R.id.editTextUserInput))
.perform(typeText(STRING_TO_BE_TYPED), closeSoftKeyboard())
onView(withId(R.id.changeTextBt)).perform(click())
onView(withId(R.id.textToBeChanged))
.check(matches(withText(STRING_TO_BE_TYPED)))
}

We have many functions in Espresso that can apply to different objects in our application. You can find a very user-friendly list of actions you can perform with Espresso in the following link: https://developer.android.com/training/testing/espresso/cheat-sheet

POM (Page Object Model) in Espresso Tests

You can also apply any design pattern you might consider suitable for your project, such as POM. We can create a couple of pages to migrate this test to POM. Let’s call them MainScreen and BasePage. MainScreen is going to control the objects and actions in the Main Screen and BasePage, and the common actions across screens:

class MainScreen : BasePage() {

private val editTextUserInput = R.id.editTextUserInput
private val changeTextBt = R.id.changeTextBt
val textToBeChanged = getView(R.id.textToBeChanged)

fun enterInputValue(value: String): MainScreen {
enterText(editTextUserInput, value)
return this
}

fun clickChangeText(): MainScreen {
clickOn(changeTextBt)
return this
}
}
open class BasePage {

fun enterText(id:Int, value:String){
onView(withId(id))
.perform(typeText(value))
}

fun clickOn(id:Int){
onView(withId(id))
.perform(click())
}

fun getView(id:Int): ViewInteraction? {
return onView(withId(id))
}
}

Now we create another test using the POM we just created:

@Test
fun modifyTextPO() {

val mainScreen = MainScreen()

mainScreen.enterInputValue(STRING_TO_BE_TYPED)
.clickChangeText()
mainScreen.textToBeChanged?
.check(matches(withText(STRING_TO_BE_TYPED)))

}

Execution

For test execution, we are using Android studio. We can just go to our run/debug configuration and create a new Android instrumented tests configuration based on our app and current test:

Run Configuration

Also, you can automatically get the test configuration by right-clicking on the function and running the test execution. For command line execution, we can do it through the Gradle wrapper: Gradlew.

Add to the build.gradle (Module: <Project Name>.app) file the following task:

task wrapper(type: Wrapper){
gradleVersion = '7.5.1'
}

Specify the version of Gradle you are using. Then, execute the following command to get the Gradle wrapper: gradle wrapper — gradle-version 7.5.1. Once we have the wrapper set up, we can go ahead and execute the following command to execute our specific Test:

./gradlew connectedDebugAndroidTest -P android.testInstrumentationRunnerArguments.class=com.example.intend.KotlinExample#modifyTextPO;

Or execute by class name:

./gradlew connectedDebugAndroidTest -P android.testInstrumentationRunnerArguments.class=com.example.intend.KotlinExample

Reporting

After execution, a default HTML report is generated by Gradle under app/build/reports/androidTests/connected directory. You will find the index.html file, which contains a summary of the results:

Summary Report
Summary Report
Class Level Report
Package Detailed Report
Test Level Report with Test Methods per class
Class Detailed Report

The reports above can be customized as it’s shown in the following image, including a screenshot, for example, or any data you might consider important to report:

Customized Report with screenshot added when test fails
Customized Test Report

We can manage to customize this report or simply add another tool to get a more detailed report if needed.

Conclusion

Espresso is a very powerful and reliable tool to use for Android UI test automation. Espresso allows users to work without a proxy between the app and the test code making execution faster and more reliable. It’s up to you to choose the right tools for automation. I hope this is useful for you and helps you get started.

--

--