Android provide instrumented tests, which are test which are run on a final device or emulator. This helps to test functionalities in a real environment. Also it enables to create automated tests for the GUI.
A helpful document is the espresso cheat sheet, which I recommend you to checkout.
Add dependencies
Creating a new android project, all needed dependencies should be already included. Also, there should be file ExampleInstrumentedTest.java. Run this file to test, if Instrumented tests work
Recomendation
Add mockito, hamcrest and awaitability to the test dependencies.
-
hamcrest has great comparison functions
-
mockito enables simple mocking
-
awaitility helps with asynchrone function tests, preventing the need for
Thread.sleep()
calls.
Following includes enables all of them:
ext {
// ...
// test libraries
mockitoVersion = "3.1.0"
hamcrestVersion = "2.1"
awaitilityVersion = "4.0.1"
}
dependencies {
// ...
testImplementation('junit:junit:4.12') {
exclude group: 'org.hamcrest' (1)
}
testImplementation('com.android.support.test:rules:1.0.2') {
exclude group: 'org.hamcrest' (1)
}
testImplementation('com.android.support.test:runner:1.0.2') {
exclude group: 'org.hamcrest' (1)
}
testImplementation "org.awaitility:awaitility:${project.awaitilityVersion}"
testImplementation "org.hamcrest:hamcrest:${project.hamcrestVersion}"
testImplementation "org.mockito:mockito-core:${project.mockitoVersion}"
androidTestImplementation('androidx.test.ext:junit:1.1.0') {
exclude group: 'org.hamcrest'
}
androidTestImplementation('androidx.test.espresso:espresso-core:3.1.1') {
exclude group: 'org.hamcrest'
}
androidTestImplementation('com.android.support.test:rules:1.0.2') { (2)
exclude group: 'org.hamcrest'
}
androidTestImplementation('com.android.support.test:runner:1.0.2') {
exclude group: 'org.hamcrest'
}
androidTestImplementation "org.awaitility:awaitility:${project.awaitilityVersion}"
androidTestImplementation "org.hamcrest:hamcrest:${project.hamcrestVersion}"
androidTestImplementation "org.mockito:mockito-core:${project.mockitoVersion}"
}
1 | needed, since awaitility is dependent on a newer hamcrest version. This will prevent build errors |
2 | if missing, strange permission errors will popup |
Simple activity start
To get a feeling for instrumented tests, lets just start an activity.
package ch.amk.exercise4.mqtt;
import android.Manifest;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.rule.GrantPermissionRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class StartMainActivityInstrumentedTest {
@Rule
public ActivityScenarioRule<MainActivity> rule = new ActivityScenarioRule<>(MainActivity.class); (1)
@Test
public void testStartingActivity() {
rule.getScenario().onActivity(activity -> { (2)
// your asserts
}); (3)
}
}
1 | Create a scenario, for every test call it will create a new Scenario, which will start the Activity. |
2 | This is not needed, but to be able to access the activity class, validation and calls to the activity needs to be done here. |
3 | Set an debug point here, this will allow keeping the activity open. |
By running this test, the emulator should start the MainActivity and close it right away.
The ActivityScenarioRule
also enables also following functionalities:
-
rule.getScenario().moveToState
change the current state of the Activity -
rule.getScenario().recreate
recreate the Activity
By using the Debug mode, it is really helpful to set an breakpoint at the end of the test and start a specific activity without changing the AndroidManifest.xml file. |
Clicking buttons
To be able to click a button, first it the button has to be found. Expresso provides the
onView
function to find an object.
// find by R.id
Espresso.onView(R.id.element_id);
// using a matcher
Espresso.onView(ViewMatchers.withText("text"));
Espresso.onView(ViewMatchers.withContentDescription("text"));
Actions can be performed on the result with the function perform()
.
// run click action
Espresso.onView(R.id.element_id).perform(ViewActions.click());
Show GUI hierarchy
The Gui hierarchy can be printed if an objected is selected, which can not be found. The hirarchy will show up in the log as exception.
Espresso.onView(ViewMatchers.withText("XYZ")).perform(ViewActions.click());
Using Edit Text
If advanced actions are needed, the UIAnimator is bringing those features. Including to wait for an Object to appear, pressing buttons, ec.
Add UIAnimator
dependencies {
// ...
androidTestImplementation 'com.android.support.test.uiautomator:uiautomator-v18:2.1.3'
}
Using the UIAnimator
To be able to use the UIAnimator
, we first have to create an UiDevice
, It is
recommended to make it a class attribute in the test:
UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
Next, like with the Espresso
, we have to select our target components:
// select by description
UiObject byDescription = device.findObject(new UiSelector().description("message_box"));
// select by R.ID
UiObject2 byRId = device.findObject(By.res(
"ch.amk.exercise4.mqtt", (1)
"message_box" (2)
));
// select by content, also has textContains for some part of content, textMatches for regex, ...
UiObject byContent = device.findObject(new UiSelector().text("Message"));
1 | The package name of the activity |
2 | The R.id, sadly R.id.message_box does not work. |
Basic JUnit assertions can then be used to test some things:
UiObject element = device.findObject(new UiSelector().description("message_box"));
// check if exists
assertTrue(element.exists());
Enter text into EditText
This can be done with Espresso
or UIAnimator
this.rule.getScenario().onActivity(activity -> {
// ViewActions do not work here
});
// change text with espresso
onView(withId(R.id.message_box))
.perform(replaceText("Hello World"));
// change text with UiAnimator
device.findObject(By.res("ch.amk.exercise4.mqtt", "message_box"))
.setText("Hello World");
Further
Further checkout those android testing samples, they contain more ideas on how tests can be executed. Also exercise 3 contains some CRUD tests combined with the recycling view, which also uses Dagger2 to mock backend services cleanly in the activity.