testing android apps with espresso
TRANSCRIPT
Testing Android Apps with Espresso
About me
Édipo Souza
Édipo Souza has over 3 years experience in android development with Degree in Sistemas e Mídias Digitais by Federal University of Ceará (UFC) and Specialization in Desenvolvimento de Sistemas para Dispositivos Móveis by Faculdade Sete de Setembro (FA7). He has certifications Oracle Certified Associate Java SE 8 Programmer and Programming in HTML5 with JavaScript and CSS3 Specialist.
● What’s Espresso?● Why use Espresso?● Configuration● Android Test Rules● Main Components● Espresso Cheat Sheet● JUnit Annotations● Extra Libraries
○ Espresso-Contrib○ Espresso-Intents○ UI Automator
● Development Tips● Commun Situations and Solution
Schedule
● “A simple API for writing reliable UI tests” - Google
● Backward Compatibility Api 8 (Froyo) or Higher
● Open Source - https://code.google.com/p/android-test-kit
● Google Product - Presented at Google Test Automation Conference 2013
● Android Tool - Added to Android Testing Support Library in 2015 with
Version 2.1 released on 2015/04/21
● Actual version 2.2
What’s Espresso?
● “The core API is small, predictable, and easy to learn and yet remains
open for customization” - Google
● Easy - “An extensive set of action APIs to automate UI interactions.”
● Extensible - “Flexible APIs for view and adapter matching in target apps.”
● Fast - “UI thread synchronization to improve test reliability.”
● No WaitFor(), No Sleep()!
● JUnit4 Support.
Why use Espresso?
● Add these lines in your biuld.gradle file
ConfigurationdefaultConfig { testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"}packagingOptions { exclude 'NOTICE.txt' exclude 'LICENSE.txt'}
● Add these dependencies in your biuld.gradle file
Configurationdependencies { androidTestCompile ('com.android.support.test.espresso:espresso-core:2.2'){exclude module: 'support-annotations'} androidTestCompile ('com.android.support.test.espresso:espresso-intents:2.2'){exclude module: 'support-annotations'} androidTestCompile ('com.android.support.test:runner:0.3'){exclude module: 'support-annotations'} androidTestCompile ('com.android.support.test:rules:0.3'){exclude module: 'support-annotations'} //Optional for uiAutomator androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.1' //Optional for RecyclerView androidTestCompile('com.android.support.test.espresso:espresso-contrib:2.2') { exclude group: 'com.android.support', module: 'appcompat' exclude group: 'com.android.support', module: 'support-v4' exclude module: 'support-annotations' exclude module: 'recyclerview-v7' }}
● Make use of JUnit4 @Rule annotation
● Extends ActivityInstrumentationTestCase2 or ServiceTestCase are deprecated
● ActivityTestRule
The activity under the Rule will be launched again in each Test@Rule
public ActivityTestRule<MyActivity> activityRule = new ActivityTestRule<>(MyActivity.class);
● ServiceTestRule
The service will start and shutdown in each Test using startService it method@Rule
public final ServiceTestRule mServiceRule = new ServiceTestRule();
Android Test Rules
OK, show me Code!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;import static android.support.test.espresso.matcher.ViewMatchers.withText;
@RunWith(AndroidJUnit4.class)public class MyActivityTest {
@Rule public ActivityTestRule<MyActivity> mActivityRule = new ActivityTestRule<>(MyActivity.class);
@Test public void testClickOKButtonAnd() {
}
}
Main ComponentsMatcher - Provide a way to find a view instance on
view hierarchy that matches a given criteria.
ViewAction - Provide instructions to perform an Action on view object.
ViewAssertion - Allows to check if some view object state matches with given criteria. This can make the Test fail.
ViewMatcherMatcher Categories
● User PropertiesCommun component attributes
● UI PropertiesCommun UI component state
● ObjectGroup, Logic and Text helpers
● HierarchyIdentifiers based on relationship
● InputType support
● ClassIdentifiers based on object class
● RootWindow holding view object
Let’s see it on Code!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;import static android.support.test.espresso.matcher.ViewMatchers.withText;
@RunWith(AndroidJUnit4.class)public class MyActivityTest {
@Rule public ActivityTestRule<MyActivity> mActivityRule = new ActivityTestRule<>(MyActivity.class);
@Test public void testClickOKButtonAnd() { //Find a button with text 'play' onView(withText(R.string.play))
}
}
Action Categories
● Click/PressCommun tap actions
● GesturesSwipe and Scroll actions
● TextEditable actions
ViewActions
Cool, but and the Code?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;import static android.support.test.espresso.matcher.ViewMatchers.withText;
@RunWith(AndroidJUnit4.class)public class MyActivityTest {
@Rule public ActivityTestRule<MyActivity> mActivityRule = new ActivityTestRule<>(MyActivity.class);
@Test public void testClickOKButtonAnd() { //Click on button with text 'play' onView(withText(R.string.play)).perform(click())
}
}
Assertions Categories
● GenericHelpers assertions
● LayoutOverlay relationship
● PositionsView positions relationship
ViewAssertions
OK, but I want see Code!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;import static android.support.test.espresso.matcher.ViewMatchers.withText;
@RunWith(AndroidJUnit4.class)public class MyActivityTest {
@Rule public ActivityTestRule<MyActivity> mActivityRule = new ActivityTestRule<>(MyActivity.class);
@Test public void testClickOKButtonAnd() { //Click on button with text 'play' and Check if text change to 'stop' onView(withText(R.string.play)).perform(click()).check(matches(withText(R.string.stop)));
}
}
//Click on button with text 'play'. Check if view with id btPlayStop has text 'stop' onView(withText(R.string.play)).perform(click()); onView(withId(R.id.btPlayStop)).check(matches(withText(R.string.stop)));
//Click on item with text 'Track 3’ in a list of strings. onData(allOf(is(instanceOf(String.class)), is("Track 3"))).perform(click());
Espresso Cheat Sheet
Junit Annotations@BeforeClass
The method is executed before all test methods
@Before
This method is executed before each test method
@AfterClass
This method is executed after all test methods
@AfterThis method is executed after each test method
@BeforeClasspublic static void setUp() { ...}
@Beforepublic static void setupForEachTest() {...}
@Testpublic void test1() {...}
@Testpublic void test2() {…}
@Afterpublic static void undoSetupForEachTest() {…}
@AfterClasspublic static void undoSetup() {…}
● Espresso-ContribContains Actions to RecyclerView and system components like Pickers
● Espresso-IntentsAllows to Check or Mock response of started intents
● Espresso-Idling-ResourceEnable to define new resources to Espresso wait before proceed
● UI AutomatorLet you do BlackBox test actions on system and third party apps
Extra Libraries
DrawerActions○ openDrawer(int drawerLayoutId)○ closeDrawer(int drawerLayoutId)
Espresso-ContribDrawerMatches
○ isOpen()○ isClosed()
RecyclerViewActions
Needed because recyclerView extends ViewGroup, not AdapterView, so no onData for
it.
Espresso-Contrib
Actions○ actionOnHolderItem(Matcher<VH> viewHolderMatcher, ViewAction viewAction)
○ actionOnItem(Matcher<View> itemViewMatcher, ViewAction viewAction)
○ actionOnItemAtPosition(int position, ViewAction viewAction)
Scroll○ scrollTo(Matcher<View> itemViewMatcher)
○ scrollToHolder(Matcher<VH> viewHolderMatcher)
○ scrollToPosition(int position)
PickerActions
Needed for interact with DatePicker and TimerPicker
Espresso-Contrib
TimePicker○ setTime(int hours, int minutes)
DatePicker○ setDate(int year, int monthOfYear, int dayOfMonth)
❖ intended(intentMatcher)
Allows validation of Intents sent out by the application under test.
Espresso-Intents❖ intending(intentMatcher)
.respondWith(activityResult)Enables stubbing the response of Intents sent by startActivityForResult
“It’s like Mockito, but for android intents.”
Matcher Categories● Intent
Commun intent parameters● URI
URI attributes validations● Component Name
Component class and package validations● Bundle
Search intent based on Entry, Key or Value
“ UI Automator is well-suited for writing black box-style automated tests”
UI Automator
UIDevice UISelector
○ findObject(UiSelector selector)○ findObjects(BySelector selector)○ takeScreenshot(File storePath)
○ pressHome() / Back / Menu / RecentApps○ pressEnder() / Search / KeyCode(int keyCode)○ click(int x, int y)○ swipe(int initX, int initY, int endX, int endY, int steps)
○ openNotification()○ openQuickSettings()○ setOrientationLeft() / Right() / Natural()○ sleep() / wakeUp() / isScreenOn()
○ checked(boolean val)○ focusable(boolean val)○ className(Class<T> type)○ description(String desc)○ resourceId(String id)○ text(String text)
○ click() / AndWaitForNewWindow() ○ exists()○ getText() / setText(String text) / clearTextField()○ swipeUp(int steps) Down/Left/Right
UIObject
Cool, I want see it on Code!import android.support.test.InstrumentationRegistry;import android.support.test.runner.AndroidJUnit4;import android.support.test.uiautomator.UiDevice;import android.support.test.uiautomator.UiSelector;
@RunWith(AndroidJUnit4.class)public class MyActivityTest {
@Testpublic void openMemoAndAddANewNote() throws Exception { UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
}}
//Go to launcher home uiDevice.pressHome(); //click apps button to open apps drawer uiDevice.findObject(new UiSelector().description("Apps")).clickAndWaitForNewWindow(); //open calculator uiDevice.findObject(new UiSelector().text("Calculator")).clickAndWaitForNewWindow(); //Calculate 3*9 uiDevice.findObject(new UiSelector().description("3")).click(); uiDevice.findObject(new UiSelector().description("Multiply")).click(); uiDevice.findObject(new UiSelector().description("9")).click(); uiDevice.findObject(new UiSelector().description("equal")).click(); //create a selector for result display text based on its resourceId UiSelector resultSelector = new UiSelector().resourceId("com.android.calculator2:id/display_Res_Text"); //check result assertEquals("equal 27", uiDevice.findObject(resultSelector).getText());
“ UI Automator is well-suited for writing black box-style automated tests”
UI Automator Viewer
DEMO
I want Espresso in Action!
● Get the application context
● Get the test application context
● Obtain the context of activity to be tested
● The withText() method can be used with a string resource id
Development Tips
InstrumentationRegistry.getTargetContext();
Test classactivityRule.getActivity().getApplicationContext();
Activity
InstrumentationRegistry.getContext();
@Rule
public ActivityTestRule<MyActivity> activityRule = new ActivityTestRule<>(MyActivity.class);
activityRule.getActivity().getContext();
withText(R.string.test)
● When is expected that the test returns an exception, simply add the expected exception to Test annotation:
● However for large testing is recommended to use use ExpectedException Rule
Development Tips
@Test(expected = IndexOutOfBoundsException.class)public void test1() {
}
@Rule
public ExpectedException thrown = ExpectedException.none();
@Testpublic void shouldTestExceptionMessage() throws IndexOutOfBoundsException {
List<Object> list = new ArrayList<Object>(); thrown.expect(IndexOutOfBoundsException.class);
thrown.expectMessage("Index: 0, Size: 0"); list.get(0); // execution will never get past this line
}
● To interact with the items from Spinner, PopupMenu or similar use:
'.inRoot(isPlatformPopup())'
● Stay tuned when performs swipe in a pageView to find an item in list.
Perform a click in a tab for that the items can be found
Commun Situations/Solution
//Clicks in spinneronView(withId(R.id.spinnerCountries)).perform(click());//Select the country Brazil and clicks in itonData(allOf(is(instanceOf(String.class)), is(“Brazil”))).inRoot(isPlatformPopup()).perform(click());
//Click TAB1onView(withText(“TAB1”)).perform(click());
● As RecyclerView extends ViewGroup should be used the onView
method to find items in list instead of onData
● If a RecyclerView item is not visible, unlike the ListView, a scroll must be
made to the item using RecyclerViewActions.scrollTo() before interacting
with the item.
Commun Situations/Solution
// Perform a click on first element in the RecyclerViewonView(withId(R.id.recyclerView)).perform(RecyclerViewActions.actionOnItemAtPosition(0, click()));
Object rvTag = 0;Matcher<View> viewMatcher = hasDescendant(withText(“Item 100”));onView(withTagValue(is(rvTag))).perform(RecyclerViewActions.scrollTo(viewMatcher));
● To check whether a toast was displayed
● To perform an action conditionally, we can override the method
withFailureHandler, then if the check fails, instead of finishing the test, the
method handle is called
Commun Situations/Solution
//Get the decorViewView activityDecorView = mActivityRule.getActivity().getWindow().getDecorView();onView(withText(TOAST_TEXT)).inRoot(withDecorView(not(activityDecorView))).check(isDisplayed());
onView(withText(R.string.dialog_config_app)).withFailureHandler(new FailureHandler() { @Override public void handle(Throwable throwable, Matcher<View> matcher) { onView(withText(android.R.string.ok)).perform(click()); }}).check(doesNotExist());
Cool Links:● https://code.google.com/p/android-test-kit/
● https://github.com/vgrec/EspressoExamples/
● http://www.vogella.com/tutorials/Mockito/article.html
● https://github.com/junit-team/junit/wiki/Exception-testing
● https://developer.android.com/tools/testing-support-library/index.html
● https://github.com/googlesamples/android-testing/tree/master/espresso
● https://androidresearch.wordpress.com/2015/04/04/an-introduction-to-espresso
Questions?