This tutorial describes how to developer user interface tests for Android applications with the Espresso testing framework. This tutorial assumes that you are familiar with Android programming in general.
1. The Espresso test framework
Espresso is a testing framework for Android to make it easy to write reliable user interface tests.
Google released the Espresso framework in Oct. 2013. Since its 2.0 release Espresso is part of the Android Support Repository.
Espresso automatically synchronizes your test actions with the user interface of your application. The framework also ensures that your activity is started before the tests run. It also let the test wait until all observed background activities have finished.
It is intended to test a single application but can also be used to test across applications. If used for testing outside your application, you can only perform black box testing, as you cannot access the classes outside of your application.
Espresso has basically three components:
-
ViewMatchers - allows to find view in the current view hierarchy
-
ViewActions - allows to perform actions on the views
-
ViewAssertions - allows to assert state of a view
The case construct for Espresso tests is the following:
onView(ViewMatcher) (1)
.perform(ViewAction) (2)
.check(ViewAssertion); (3)
1 | - Finds the view |
2 | - Performs an action on the view |
3 | - Validates a assertioin |
The following code demonstrates the usage of the Espresso test framework.
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
// image more code here...
// test statement
onView(withId(R.id.my_view)) // withId(R.id.my_view) is a ViewMatcher
.perform(click()) // click() is a ViewAction
.check(matches(isDisplayed())); // matches(isDisplayed()) is a ViewAssertion
// new test
onView(withId(R.id.greet_button))
.perform(click())
.check(matches(not(isEnabled()));
If Espresso does not find a view via the ViewMatcher
, it includes the whole view hierarchy into the error message.
That is useful for analyzing the problem.
2. Making Espresso available
2.2. Configuration of the Gradle build file for Espresso
To use Espresso for your tests, add the following dependency to the Gradle build file of your app.
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
testImplementation 'junit:junit:4.12'
// Android runner and rules support
androidtestImplementation 'com.android.support.test:runner:0.5'
androidtestImplementation 'com.android.support.test:rules:0.5'
// Espresso support
androidtestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
// add this for intent mocking support
androidtestImplementation 'com.android.support.test.espresso:espresso-intents:2.2.2'
// add this for webview testing support
androidtestImplementation 'com.android.support.test.espresso:espresso-web:2.2.2'
}
Ensure that the android.support.test.runner.AndroidJUnitRunner
is specified as value for the testInstrumentationRunner
parameter in the build file of your app.
Via the packagingOptions
you may have to exclude LICENSE.txt
, depending on the libraries you are using.
The following listing is an example for that.
apply plugin: 'com.android.application'
android {
compileSdkVersion 22
buildToolsVersion '22.0.1'
defaultConfig {
applicationId "com.example.android.testing.espresso.BasicSample"
minSdkVersion 10
targetSdkVersion 22
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
packagingOptions {
exclude 'LICENSE.txt'
}
lintOptions {
abortOnError false
}
}
dependencies {
// as before.......
}
2.3. Device settings
It is recommended to turn off the animation on the Android device which is used for testing. Animations might confusing Espressos check for ideling resources.
3. Exercise: A first Espresso test
3.1. Create project under test
Create a new Android project called Espresso First with the package name com.vogella.android.espressofirst. Use the Blank Template as basis for this project.
Change the generated activity_main.xml layout file to the following.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<EditText
android:id="@+id/inputField"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:id="@+id/changeText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="New Button" android:onClick="onClick"/>
<Button
android:id="@+id/switchActivity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Change Text" android:onClick="onClick"/>
</LinearLayout>
Create a new file called activity_second.xml.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="Large Text"
android:id="@+id/resultView" />
</LinearLayout>
Create a new activity with the following code.
package com.vogella.android.espressofirst;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
public class SecondActivity extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
TextView viewById = (TextView) findViewById(R.id.resultView);
Bundle inputData = getIntent().getExtras();
String input = inputData.getString("input");
viewById.setText(input);
}
}
Also adjust your MainActivity
class.
package com.vogella.android.espressofirst;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
public class MainActivity extends Activity {
EditText editText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
editText = (EditText) findViewById(R.id.inputField);
}
public void onClick(View view) {
switch (view.getId()) {
case R.id.changeText:
editText.setText("Lalala");
break;
case R.id.switchActivity:
Intent intent = new Intent(this, SecondActivity.class);
intent.putExtra("input", editText.getText().toString());
startActivity(intent);
break;
}
}
}
3.2. Adjust the app build.gradle
Perform the setting of the configuration of the Gradle build file for Espresso.
3.3. Create your Espresso test
package com.vogella.android.espressofirst;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.closeSoftKeyboard;
import static android.support.test.espresso.action.ViewActions.typeText;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
@RunWith(AndroidJUnit4.class)
public class MainActivityEspressoTest {
@Rule
public ActivityTestRule<MainActivity> mActivityRule =
new ActivityTestRule<>(MainActivity.class);
@Test
public void ensureTextChangesWork() {
// Type text and then press the button.
onView(withId(R.id.inputField))
.perform(typeText("HELLO"), closeSoftKeyboard());
onView(withId(R.id.changeText)).perform(click());
// Check that the text was changed.
onView(withId(R.id.inputField)).check(matches(withText("Lalala")));
}
@Test
public void changeText_newActivity() {
// Type text and then press the button.
onView(withId(R.id.inputField)).perform(typeText("NewText"),
closeSoftKeyboard());
onView(withId(R.id.switchActivity)).perform(click());
// This view is in a different Activity, no need to tell Espresso.
onView(withId(R.id.resultView)).check(matches(withText("NewText")));
}
}
3.4. Run your test
Right-click on your test and select Run.
4. More on writing Espresso unit tests
4.1. Location of Espresso tests and required static imports
Espresso tests must be placed in the app/src/androidTest folder.
To simplify the usage of the Espresso API it is recommended to add the following static imports. This allows to access these methods without the class prefix.
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.closeSoftKeyboard;
import static android.support.test.espresso.action.ViewActions.typeText;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
4.2. Using ViewMatcher
To find a view, use the onView()
method with a view matcher which selects the correct view.
If you are using an AdapterView use the onData()
method instead of the onView()
method. The
The onView()
methods return an object of type ViewInteraction
.
The onData()
method returns an object of type DataInteraction
.
The following table describes the available matchers.
ViewMatcher | Description |
---|---|
withText("SOMETEXT") |
Searches for a view with the specified text, for example onView(withText("User")) searches for a view with the text "User". |
withId() |
Similar to withText but searches for the ID |
Hamcrest Matchers |
You can use Hamcrest matchers, e.g., |
4.3. Performing Actions
ViewInteraction
and DataInteraction
allow to specify an action for test via an object of type ViewAction
via the perform
method.
The ViewActions
class provides helper methods for the most common actions, like:
-
ViewActions.click
-
ViewActions.typeText()
-
ViewActions.pressKey()
-
ViewActions.clearText()
The perform
method returns again an object of type ViewInteraction
on which you can perform more actions or validate the result.
It also uses varags as argument, i.e, you can pass several actions at the same time to it.
4.4. Verifying test results
Call the ViewInteraction.check()
method to assert a view state.
This method expects a ViewAssertion
object as input.
The ViewAssertions
class provides helper methods for creating these objects:
-
matches - Hamcrest matcher
-
doesNotExist - asserts that the select view does not exist
You can use the powerful Hamcrest matchers. The following gives a few examples:
onView(withText(startsWith("ABC"))).perform(click()); (1)
onView(withText(endsWith("YYZZ"))).perform(click()); (2)
onView(withId(R.id.viewId)).check(matches(withContentDescription(containsString("YYZZ")))); (3)
onView(withText(equalToIgnoringCase("xxYY"))).perform(click()); (4)
-
onView(withText(equalToIgnoringWhiteSpace("XX YY ZZ"))).perform(click()); (5)
onView(withId(R.id.viewId)).check(matches(withText(not(containsString("YYZZ"))))); (6)
1 | Matches a view which text starts with "ABC" pattern |
2 | Matches a view which text ends with "YYZZ" pattern |
3 | Matches that the text of the view with specified R.id has content description which contains "YYZZ" string anywhere |
4 | Matches a view which text is equal to the specified string, ignoring case: |
5 | Matches a view which text is equal to the specified text when whitespace differences are (mostly) ignored |
6 | Matches that text of a particular view with specified R.id does not contain "YYZZ" string |
4.5. Access to the instrumentation API
Via the InstrumentationRegistry.getTargetContext()
you have access to the target context of your application.
For example, if you want to use the id without using R.id you can use the following helper method to determine it.
package testing.android.vogella.com.asynctask;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
@RunWith(AndroidJUnit4.class)
public class EspressoTest {
@Rule
public ActivityTestRule<MainActivity> mActivityRule =
new ActivityTestRule<>(MainActivity.class);
@Test
public void buttonShouldUpdateText(){
onView(withId(R.id.update)).perform(click());
onView(withId(getResourceId("Click"))).check(matches(withText("Done")));
}
private static int getResourceId(String s) {
Context targetContext = InstrumentationRegistry.getTargetContext();
String packageName = targetContext.getPackageName();
return targetContext.getResources().getIdentifier(s, "id", packageName);
}
}
4.6. Configuring the start intent for the activity
If you specify false
as third parameter in the ActivityTestRule
, you can configure the intent for starting the activity.
This is as demonstrated in the following code example.
package com.vogella.android.testing.espressosamples;
import android.content.Intent;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
@RunWith(AndroidJUnit4.class)
public class SecondActivityTest {
@Rule
// third parameter is set to false which means the activity is not started automatically
public ActivityTestRule<SecondActivity> rule =
new ActivityTestRule(SecondActivity.class, true, false);
@Test
public void demonstrateIntentPrep() {
Intent intent = new Intent();
intent.putExtra("EXTRA", "Test");
rule.launchActivity(intent);
onView(withId(R.id.display)).check(matches(withText("Test")));
}
}
4.7. Adapter views
AdapterView is a special type of widget that loads its data dynamically from an adapter.
Only a subset of the data has real views in the current view hierarchy.
A onView()
search would not find views for them.
onData
can be used to interactive with adapter views, like ListView
.
The following gives a few examples.
// click on an item of type String in a spinner
// afterwards verify that the view with the R.id.spinnertext_simple id contains "Eclipse"
onData(allOf(is(instanceOf(String.class)), is("Eclipse"))).perform(click());
onView(withId()).check(matches(withText(containsString("Eclipse")))); // normal view not adapter view
onData(allOf(is(instanceOf(Map.class)), hasEntry(equalTo("STR"), is("item: 50"))).perform(click());
onData(withItemContent("item: 60")).onChildView(withId(R.id.item_size)).perform(click());
4.8. Espresso testing with permissions
Via instrumentation you can grand your tests the permission to execute.
@Before
public void grantPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
getInstrumentation().getUiAutomation().executeShellCommand(
"pm grant " + getTargetContext().getPackageName()
+ " android.permission.CALL_PHONE");
}
}
4.9. Espresso UI recorder
Android Studio provides an
menu entry which allows you to record the interaction with your application and create a Espresso test from it.4.10. Configuring the activity under test
You can also access the activity object which you are testing and call methods on it. For example, assume you want to call a method on your activity.
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void configureMainActivity(String Uri) {
// do something with this
}
}
This configureMainActivity
can be called in your test.
package com.vogella.android.myapplication;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Rule
public ActivityTestRule<MainActivity> mActivityRule =
new ActivityTestRule<MainActivity>(MainActivity.class);
@Test
public void useAppContext() throws Exception {
MainActivity activity = mActivityRule.getActivity();
activity.configureMainActivity("https://www.vogella.com/");
// do more
}
}
You can also override methods in |
You can also access the current activity.
@Test
public void navigate() {
Activity instance = getActivityInstance();
onView(withText("Next")).perform(click());
Activity activity = getActivityInstance();
boolean b = (activity instanceof SecondActivity);
assertTrue(b);
// do more
}
public Activity getActivityInstance() { (1)
final Activity[] activity = new Activity[1];
InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable( ) {
public void run() {
Activity currentActivity = null;
Collection resumedActivities = ActivityLifecycleMonitorRegistry.getInstance().getActivitiesInStage(RESUMED);
if (resumedActivities.iterator().hasNext()){
currentActivity = (Activity) resumedActivities.iterator().next();
activity[0] = currentActivity;
}
}
});
return activity[0];
}
1 | - Allows to access the currently active activity. |
ActivityLifecycleMonitorRegistry is not API so this might change. |
4.12. Checking for a toast
There is an example how you can click on an list item and check for a toast to be displayed.
package com.vogella.android.test.juntexamples;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static android.support.test.espresso.Espresso.onData;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.RootMatchers.withDecorView;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.hasToString;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;
@RunWith(AndroidJUnit4.class)
public class MainActivityTestList {
@Rule
public ActivityTestRule<MainActivity> rule = new ActivityTestRule<>(MainActivity.class);
@Test
public void ensureListViewIsPresent() throws Exception {
onData(hasToString(containsString("Frodo"))).perform(click());
onView(withText(startsWith("Clicked:"))).
inRoot(withDecorView(
not(is(rule.getActivity().
getWindow().getDecorView())))).
check(matches(isDisplayed()));
}
}
5. Mocking intents with Espresso Intents
Espresso provides also the option to mock intents. This allows you to check if an activity has issued the correct intents and reacts correct if it receives the correct intent results.
Espressos intents is provided by the com.android.support.test.espresso:espresso-intents
library.
If you want to use Espresso intent in your Espresso tests, use the IntentsTestRule
instead of ActivityTestRule
.
package testing.android.vogella.com.simpleactivity;
import android.support.test.espresso.intent.rule.IntentsTestRule;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.intent.Intents.intended;
import static android.support.test.espresso.intent.matcher.IntentMatchers.toPackage;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
@RunWith(AndroidJUnit4.class)
public class TestIntent {
@Rule
public IntentsTestRule<MainActivity> mActivityRule =
new IntentsTestRule<>(MainActivity.class);
@Test
public void triggerIntentTest() {
onView(withId(R.id.button)).perform(click());
intended(allOf(
hasAction(Intent.ACTION_CALL),
hasData(INTENT_DATA_PHONE_NUMBER),
toPackage(PACKAGE_ANDROID_DIALER)));
}
}
For more information see https://google.github.io/android-testing-support-library/docs/espresso/intents/.
6. Using Espresso to test asynchronous code
Testing asynchronous without framework support is challenging.
The typical approach before Espresso was to wait for a predefined time.
Or to use an instance of the CountDownLatch
class in your test code and signal from the asynchronous processing that the processing was done.
Espresso makes this much easier as it monitors automatically the thread pool behind the AsynchronousTask
.
It also monitors the event queue of the user interfacevand continues only with its test once no task is running.
If you use different resources, like an IntentService you need to implement an IdlingResource
.
This implementation must monitor this resource and register this monitor with the Espresso framework.
package com.vogella.android.espressointentservice;
import android.app.ActivityManager;
import android.content.Context;
import android.support.test.espresso.IdlingResource;
import java.util.List;
public class IntentServiceIdlingResource implements IdlingResource {
ResourceCallback resourceCallback;
private Context context;
public IntentServiceIdlingResource(Context context) {
this.context = context;
}
@Override
public String getName() {
return IntentServiceIdlingResource.class.getName();
}
@Override
public void registerIdleTransitionCallback(
ResourceCallback resourceCallback) {
this.resourceCallback = resourceCallback;
}
@Override
public boolean isIdleNow() {
boolean idle = !isIntentServiceRunning();
if (idle && resourceCallback != null) {
resourceCallback.onTransitionToIdle();
}
return idle;
}
private boolean isIntentServiceRunning() {
ActivityManager manager =
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
// Get all running services
List<ActivityManager.RunningServiceInfo> runningServices =
manager.getRunningServices(Integer.MAX_VALUE);
// check if our is running
for (ActivityManager.RunningServiceInfo info : runningServices) {
if (MyIntentService.class.getName().equals(
info.service.getClassName())) {
return true;
}
}
return false;
}
}
package com.vogella.android.espressointentservice;
import android.app.Instrumentation;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.espresso.Espresso;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.notNullValue;
@RunWith(AndroidJUnit4.class)
public class IntegrationTest {
@Rule
public ActivityTestRule rule = new ActivityTestRule(MainActivity.class);
IntentServiceIdlingResource idlingResource;
@Before
public void before() {
Instrumentation instrumentation
= InstrumentationRegistry.getInstrumentation();
Context ctx = instrumentation.getTargetContext();
idlingResource = new IntentServiceIdlingResource(ctx);
Espresso.registerIdlingResources(idlingResource);
}
@After
public void after() {
Espresso.unregisterIdlingResources(idlingResource);
}
@Test
public void runSequence() {
// this triggers our intent service, as we registered
// Espresso for it, Espresso wait for it to finish
onView(withId(R.id.action_settings)).perform(click());
onView(withText("Broadcast")).check(matches(notNullValue()));
}
}
7. Exercise: Creating a custom Espresso matcher
Android provides the BoundedMatcher
class which allows to create Espresso view matchers for specific view types.
Lets write a matcher which checks the text hints of an EditText field.
public static Matcher<View> withItemHint(String itemHintText) {
checkArgument(!(itemHintText.equals(null)));
return withItemHint(is(itemHintText));
}
public static Matcher<View> withItemHint(final Matcher<String> matcherText) {
// use preconditions to fail fast when a test is creating an invalid matcher.
checkNotNull(matcherText);
return new BoundedMatcher<View, EditText>(EditText.class) {
@Override
public void describeTo(Description description) {
description.appendText("with item hint: " + matcherText);
}
@Override
protected boolean matchesSafely(EditText editTextField) {
return matcherText.matches(editTextField.getHint().toString());
}
};
}
It can be used via the following code:
import static com.your.package.test.Matchers.withItemHint;
...
onView(withItemHint("test")).check(matches(isDisplayed()));
8. Exercise: Write a test for an intent with Espresso
8.1. Create project which is tested
Create a new Android project with the testing.android.vogella.com.simpleactivity package and the Empty Activity template.
Add a second activity called SecondActivity
to your project via .
This activity should use a layout with at least one TextView
.
The id of the TextView
should be "resultText" and its text should be set to "Started".
Add an EditText
field to the layout of the MainActivity
class.
Add a button to the layout used by MainActivity. If this button is clicked, the second activity should be started.
Put the text EditText
field as extra into the intent using "text" as key.
Also put the "https://www.vogella.com/" String as extra into the intent using the key "URL".
Here is some example code for the MainActivity
.
package testing.android.vogella.com.simpleactivity;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void onClick(View view) {
Intent intent = new Intent(this, SecondActivity.class);
intent.putExtra("URL", "https://www.vogella.com/");
startActivity(intent);
}
}
8.2. Write tests
Write a Espresso test for the activity which tests the following:
-
Check that the layout of the MainActivity contains a button with the
R.id.button
ID -
Ensure that the text on the button is "Start new activity"
-
Ensure that if the getActivity.onClick() method is called, that the correct intent is triggered. This intent should contain the String extra (hasExtra("URL", "https://www.vogella.com/").
8.3. Validate
Your test code should look similar to the following example code.
package testing.android.vogella.com.simpleactivity;
import android.support.test.espresso.intent.rule.IntentsTestRule;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.intent.Intents.intended;
import static android.support.test.espresso.intent.matcher.IntentMatchers.hasExtra;
import static android.support.test.espresso.intent.matcher.IntentMatchers.toPackage;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.core.IsNull.notNullValue;
@RunWith(AndroidJUnit4.class)
public class TestIntent {
@Rule
public IntentsTestRule<MainActivity> mActivityRule = new IntentsTestRule<>(MainActivity.class);
@Test
public void triggerIntentTest() {
// check that the button is there
onView(withId(R.id.button)).check(matches(notNullValue() ));
onView(withId(R.id.button)).check(matches(withText("Start new activity")));
onView(withId(R.id.button)).perform(click());
intended(toPackage("testing.android.vogella.com.simpleactivity"));
intended(hasExtra("URL", "https://www.vogella.com/"));
}
}
9. Exercise: functional test for activities
In this exercise, you start a second activity and validate that is has been started.
9.1. Write functional test for activities
package testing.android.vogella.com.simpleactivity;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
@RunWith(AndroidJUnit4.class)
public class TestSecondActivityIsStarted {
@Rule
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class);
@Test
public void validateSecondActivity() {
// check that the button is there
onView(withId(R.id.button)).perform(click());
onView(withId(R.id.resultText))
.check(matches(withText(("Started"))));
}
}
To test the direct modification of a view, create the following test class for the SecondActivity
class.
package testing.android.vogella.com.simpleactivity;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.Espresso.pressBack;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
@RunWith(AndroidJUnit4.class)
public class SecondActivityFunctionalTest {
@Rule
public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class);
@Test
public void validateSecondActivity() {
// check that the button is there
onView(withId(R.id.button)).perform(click());
onView(withId(R.id.resultText))
.check(matches(withText(("Started"))));
pressBack();
onView(withId(R.id.button))
.check(matches(withText(("Start new activity"))));
}
}
10. Exercise: Testing asynchronous code with Espresso
Create an project with the package called testing.android.vogella.com.asynctask
which allows to trigger an AsyncTask via a button.
The example code for the activity:
package testing.android.vogella.com.asynctask;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void onClick(View view) {
TextView textView = (TextView) findViewById(R.id.text);
textView.setText("Running");
myTask.execute("test");
}
final AsyncTask<String, Void, String> myTask = new AsyncTask<String, Void, String>() {
@Override
protected String doInBackground(String... arg0) {
return "Long running stuff";
}
@Override
protected void onPostExecute(String result) {
TextView textView = (TextView) findViewById(R.id.text);
textView.setText("Done");
}
};
}
The example code for the test:
package testing.android.vogella.com.asynctask;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
@RunWith(AndroidJUnit4.class)
public class EspressoTest {
@Rule
public ActivityTestRule<MainActivity> mActivityRule =
new ActivityTestRule<>(MainActivity.class);
@Test
public void buttonShouldUpdateText(){
onView(withId(R.id.update)).perform(click());
onView(withId(R.id.text)).check(matches(withText("Done")));
}
}
10.1. Espresso resources
Git repository for the Android Testing - Checkout the android-support-test
branch for the testing libraries.
10.2. vogella Java example code
If you need more assistance we offer Online Training and Onsite training as well as consulting