Before we start writing our first UI test I want to describe our development process.
- First, we write a test for a UI or logic that does not yet exist.
- We expect to see build errors or failed tests.
- Then we create a UI element or write program logic.
- Finally, we run the test and make sure it passes.
This workflow is sometimes called test-driven development. Which means writing tests before creating the actual program.
Just to remind us again, our app will have three components:
- An EditText where user enters name.
- A "Greet" Button.
- And a TextView that will show the greeting message.
Each control on the screen will have a unique ID. Those IDs will be used in our tests to get those controls and interact with them. The IDs are: greet_edit_text
, greet_button
and message_text_view
.
Let's create a new test method. First, we get the activity object. Put this method into your MainActivityTests
file which you created in the previous part of the tutorial.
public void testGreet() {
MainActivity activity = getActivity();
}
At this stage your MainActivityTests file is the following:
package com.mycompany.greeter;
import android.test.ActivityInstrumentationTestCase2;
public class MainActivityTests extends ActivityInstrumentationTestCase2<MainActivity> {
public MainActivityTests() {
super(MainActivity.class);
}
public void testActivityExists() {
MainActivity activity = getActivity();
assertNotNull(activity);
}
public void testGreet() {
MainActivity activity = getActivity();
}
}
Run the tests and watch them pass. Excellent!
The test will enter name into a EditText control, which does not exist yet. Let's get an EditText control by its ID greet_edit_text
.
final EditText nameEditText =
(EditText) activity.findViewById(R.id.greet_edit_text);
You will need to add this import statement to the top of the MainActivityTests file:
import android.widget.EditText;
Your testGreet
method will look like this.
public void testGreet() {
MainActivity activity = getActivity();
final EditText nameEditText =
(EditText) activity.findViewById(R.id.greet_edit_text);
}
You will notice an error for the greet_edit_text
ID. That's because the EditText does not exist yet in our app layout. Let's create it.
- Expand app > res > layout folder in the Project tool window and open content_main.xml file.
- Switch main window from from Design to Text mode. You will see the XML markup for the layout.
- Remove automatically generated TextView element with "Hello world!" message.
- Add the following ExitText element between RelativeLayout opening and closing tags:
<EditText
android:id="@+id/greet_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:inputType="textCapSentences"/>
Your content_main.xml
will look like this:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="com.mycompany.greeter.MainActivity"
tools:showIn="@layout/activity_main">
<EditText
android:id="@+id/greet_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:inputType="textCapSentences"/>
</RelativeLayout>
You will notice that the error in your test file is fixed now. Run the tests and they will pass. Very good!
Now our test will enter a name into the EditText input.
- Switch back to MainActivityTests file.
- Add the following code to the end of the
testGreet
method.
getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
nameEditText.requestFocus();
}
});
getInstrumentation().waitForIdleSync();
getInstrumentation().sendStringSync("Jake");
This is what this code does:
- Selects the text input by calling
nameEditText.requestFocus()
in the main thread of the app. - Waits for application to be idle:
waitForIdleSync()
. - Enters text "Jake" into the input:
sendStringSync("Jake")
.
If you are wondering why we need so much code and call runOnMainSync
, waitForIdleSync
methods you are not alone. I don't have a slightest idea.
The full testGreet
method will be:
public void testGreet() {
MainActivity activity = getActivity();
// Type name in text input
// ----------------------
final EditText nameEditText =
(EditText) activity.findViewById(R.id.greet_edit_text);
// Send string input value
getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
nameEditText.requestFocus();
}
});
getInstrumentation().waitForIdleSync();
getInstrumentation().sendStringSync("Jake");
}
Let's run the tests and watch in amazement how it enters the text into the text field. Everything will be green. Great!
It is time to implement the "Greet" button. Let's start with the test, as usual. Add the following code to the end of testGreet
method:
Button greetButton =
(Button) activity.findViewById(R.id.greet_button);
TouchUtils.clickView(this, greetButton);
This code does two things:
- Gets a Button by its ID
greet_button
. - Taps the button.
Add the new imports to the top of the test file;
import android.test.TouchUtils;
import android.widget.Button;
This is the full code of testGreet method:
public void testGreet() {
MainActivity activity = getActivity();
// Type name in text input
// ----------------------
final EditText nameEditText =
(EditText) activity.findViewById(R.id.greet_edit_text);
// Send string input value
getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
nameEditText.requestFocus();
}
});
getInstrumentation().waitForIdleSync();
getInstrumentation().sendStringSync("Jake");
getInstrumentation().waitForIdleSync();
// Tap "Greet" button
// ----------------------
Button greetButton =
(Button) activity.findViewById(R.id.greet_button);
TouchUtils.clickView(this, greetButton);
}
The test will have one remaining error for the missing greet_button
ID, which is expected.
Switch back to content_main.xml file and add the following text after the EditText element:
<Button
android:id="@+id/greet_button"
android:text="@string/greet_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/greet_edit_text"
android:layout_centerHorizontal="true" />
The full layout code will be:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="com.mycompany.greeter.MainActivity"
tools:showIn="@layout/activity_main">
<EditText
android:id="@+id/greet_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:inputType="textCapSentences"/>
<Button
android:id="@+id/greet_button"
android:text="@string/greet_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/greet_edit_text"
android:layout_centerHorizontal="true" />
</RelativeLayout>
You will notice that @string/greet_button
attribute value looks red. This is the caption of the Greet button that is taken from the string resources and it is currently missing.
Add the button's caption text to the string resource file:
- Expand app > res > values in project window.
- Open strings.xml file.
- Add the following element after the last string element and before the closing
</resources>
tag.
<string name="greet_button">Greet</string>
The full contents of string.xml file will be the following:
<resources>
<string name="app_name">Greeter</string>
<string name="action_settings">Settings</string>
<string name="greet_button">Greet</string>
</resources>
Run the tests and see them pass. Well done!
We are almost there. When user taps the "Greet" button the app will show the greeting message: "Hello, [name]!". The [name] part is replaced with the name that the user entered into the EditText input. We will write the test first and then implement this logic in the app.
Switch to MainActivityTests and add this code to the end of testGreet
method.
TextView greetMessage =
(TextView) activity.findViewById(R.id.message_text_view);
String actualText = greetMessage.getText().toString();
assertEquals("Hello, Jake!", actualText);
This code does the following:
- Gets the TextView element by its ID
message_text_view
. - Obtains the text of the TextView element by calling
getText
method. - Calls
assertEquals
to compare the expected message "Hello, Jake!" with the actual message from the TextView.
You will need to add a new import to the top of the test file:
import android.widget.TextView;
The full testGreet
method will be:
public void testGreet() {
MainActivity activity = getActivity();
// Type name in text input
// ----------------------
final EditText nameEditText =
(EditText) activity.findViewById(R.id.greet_edit_text);
// Send string input value
getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
nameEditText.requestFocus();
}
});
getInstrumentation().waitForIdleSync();
getInstrumentation().sendStringSync("Jake");
getInstrumentation().waitForIdleSync();
// Tap "Greet" button
// ----------------------
Button greetButton =
(Button) activity.findViewById(R.id.greet_button);
TouchUtils.clickView(this, greetButton);
// Verify greet message
// ----------------------
TextView greetMessage = (TextView) activity.findViewById(R.id.message_text_view);
String actualText = greetMessage.getText().toString();
assertEquals("Hello, Jake!", actualText);
}
Everything should be fine, the only problem is that message_text_view
ID is missing.
Switch to content_main.xml and add the following text after the Button element:
<TextView
android:id="@+id/message_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/greet_button"
android:gravity="center"
android:textSize="30sp"/>
The full layout code will be:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="com.mycompany.greeter.MainActivity"
tools:showIn="@layout/activity_main">
<EditText
android:id="@+id/greet_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:inputType="textCapSentences"/>
<Button
android:id="@+id/greet_button"
android:text="@string/greet_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/greet_edit_text"
android:layout_centerHorizontal="true" />
<TextView
android:id="@+id/message_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/greet_button"
android:gravity="center"
android:textSize="30sp"/>
</RelativeLayout>
Run the test and notice how testGreet
fails. We can find the following in the Run tool window:
- Failure reason:
expected:<[Hello, Jake!]> but was:<[]>
- Failure location:
MainActivityTests.java
.
Click on failure location link (MainActivityTests.java) and it will bring us to this line in the MainActivityTests file:
assertEquals("Hello, Jake!", actualText);
The test failed because we have not implemented the output of the greeting message in the app yet. That message stays empty instead of showing the greeting text. Let's fix it.
There is just one thing left to be done. Let's remind us again what the app does. When the user taps the "Greet" button, the app shows a greeting message. We now need to write code that is executed when the user taps the "Greet" button.
- Open content_main.xml file.
- Add
android:onClick="didTapGreetButton"
attribute to the Button element.
The button element will look like:
<Button
android:id="@+id/greet_button"
android:text="@string/greet_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/greet_edit_text"
android:layout_centerHorizontal="true"
android:onClick="didTapGreetButton"/>
The full layout code will be the following:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="com.mycompany.greeter.MainActivity"
tools:showIn="@layout/activity_main">
<EditText
android:id="@+id/greet_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:inputType="textCapSentences"/>
<Button
android:id="@+id/greet_button"
android:text="@string/greet_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/greet_edit_text"
android:layout_centerHorizontal="true"
android:onClick="didTapGreetButton"/>
<TextView
android:id="@+id/message_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/greet_button"
android:gravity="center"
android:textSize="30sp"/>
</RelativeLayout>
You will see that didTapGreetButton is highlighted and reports this error message:
Cannot resolve symbol 'didTapGreetButton'...
We will now implement the last bit of code. It will be the didTapGreetButton
method that will show the greeting message.
- Expand app > java > com.mycompany.greeter module. Note that this time we are editing the app's main module and not the test one.
- Open MainActivity class.
- Add the following method to it.
public void didTapGreetButton(View view) {
EditText greetEditText =
(EditText) findViewById(R.id.greet_edit_text);
String name = greetEditText.getText().toString();
String greeting = String.format("Hello, %s!", name);
TextView messageTextView =
(TextView) findViewById(R.id.message_text_view);
messageTextView.setText(greeting);
}
This didTapGreetButton method does the following:
- First, it finds EditText element by ID
greet_edit_text
. - Gets the text from the input.
- Next, it constructs the greeting message with the format "Hello, %s!".
- Finds the TextView by its ID
message_text_view
. - And finally, shows the greeting message in the text view by calling
setText
method.
Add these missing imports to the top of the MainActivity file.
import android.widget.EditText;
import android.widget.TextView;
If we run the tests they should all pass. Next, try running the app and see it working. Let’s tell the boss!
Create by Evgenii.
You can see other best Android Gists or offer your just here https://github.com/lopspower/BestAndroidGists 👍.