Android UI Testing With Espresso
Testing your application to ensure it functions as expected is also as important as written code. One big mistake we all make at some point is releasing applications without testing and when it gets into production, we might start noticing some bugs.
Given that it is crucial for testing applications, we will see how to perform UI testing in a simple android application using Espresso . Not to take much time, Espresso is used to test user interactions within an android application.Say you have a login form where the user types their user name, password, and then hits the submit button and if certain criteria are met, the user is logged in successfully.
Precise Example
The application we will be testing has two activities, which are MainActivity and WelcomeActivity. Now for a login to be successful, the following must be met
- User name must not be empty
- Password must be greater than or equal to 6 in length
Here are the screens
On a successful login, the middle screen appears(WelcomeActivity), while on failure to login, loginButton disappears and the failedLogin text view at the bottom becomes visible.
Create a new Android studio project to follow along. Add the following dependencies in your app/build.gradle file
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.2.7"implementation 'androidx.appcompat:appcompat:1.0.0'implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha2'testImplementation 'junit:junit:4.12'androidTestImplementation 'androidx.test:runner:1.1.0-beta02'androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0-beta02'androidTestImplementation 'androidx.test:rules:1.1.0-beta02'
androidTestImplementation 'androidx.test.ext:junit:1.0.0-beta02'
As seen, we added the espresso dependency and other test dependencies like junit 4.
We are using kotlin android extensions as well, thus we won’t be using any findViewByIds. Here is the code for activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<EditText
android:id="@+id/username"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="60dp"
android:ems="10"
android:hint="User Name"
android:inputType="textPersonName"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
<EditText
android:id="@+id/password"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="56dp"
android:ems="10"
android:hint="Password"
android:inputType="textPassword"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.503"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/username" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:text="@string/app_name"
android:textAppearance="@style/TextAppearance.AppCompat.Title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/buttonLogin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="8dp"
android:text="Login"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/password" />
<TextView
android:id="@+id/loginFailure"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="16dp"
android:text="Failed to login"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
As seen, loginFailure’s visibility is initially set to gone.
Here is the code for MainActivity.java
package com.apps.ngenge.espressotest
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import android.view.View
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
buttonLogin.setOnClickListener {
if (username.text.isNotEmpty() && password.text.length >= 6)
{
startActivity(Intent(this@MainActivity,WelcomeActivity::class.java))
}
else{
loginFailure.visibility = View.VISIBLE
buttonLogin.visibility = View.INVISIBLE
}
}
}
}
Steps to Perform the UI Test
The steps required to perform any test on the UI are as follows
- Locate the View(for example EditText)
- Perform action(for example typing text)
- Verify if condition holds(for example, is ImageView displayed?)
The first step is performed using Espresso’s onView method. You can verify button with text “Hello World”,with id R.id.helloButton etc.
Let’s get this example from Espresso Training Guide .
@Test
fun greeterSaysHello() { onView(withId(R.id.name_field)).perform(typeText("Steve"));
onView(withId(R.id.greet_button)).perform(click());
onView(withText("Hello Steve!")).check(matches(isDisplayed()));}
Here are the steps taken above in the order specified
- Locate EditText with id name_field
- Type the word “Steve” in the name field
- Locate the button with id greet_button and click
- Check if the View with text “Hello Steve” is displayed.
If the above steps are passed, then this test case is successful otherwise, the test will fail.
To follow, change your project view to Android,open the androidTest package and create a LoginTest class file.
To create a test for MainActivity, we create what is referred to as an activity test rule for MainActivity(activity currently under test),then we write our tests. Here is the code.
@RunWith(AndroidJUnit4::class)
class LoginTest{
@JvmField
@Rule
var mActivityRule:ActivityTestRule<MainActivity> = ActivityTestRule(MainActivity::class.java)
@Test fun test_Login_HasPasswordOfMoreThan6Letters()
{
onView(withId(R.id.username))
.perform(typeText("ngengesenior"))
onView(withId(R.id.password))
.perform(typeText("password"))
onView(withId(R.id.buttonLogin))
.check(matches(isDisplayed()))
.perform(click())
onView(withId(R.id.appIcon))
.check(matches(isDisplayed()))
}
}
Explanation of code
- We create our LoginTest class and annotate it with @RunWith(AndroiJUnit4) class.
- Create mActivityTestRule variable for MainActivity(Activity we want to run test on).
- create our test function ,test_Login_HasPasswordOfMoreThan6Letters and annotate it with, @Test .
- Locate EditText with id , username and perform an action by typing the text,”ngengesenior”.
- Locate EditText with id,password and perform action by typing the text,”password”.
- Check if button with id,buttonLogin is displayed and click the button
- Check if the ImageView with id,appIcon is displayed.
In this case, test is passed since our user name is not empty,our password is greater than 6.
Here is how the automated test works
I actually manged to use androidx and Kotlin in this project.
Here is link to full code on Github,