Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Espresso UI Testing on Android

Espresso UI Testing on Android

Introduction to UI testing with Espresso on Android. Slides presented on #7 Poznań Android Developer Group meetup - http://www.meetup.com/Poznan-Android-Developer-Group/

Sample project: https://github.com/mrmike/EspressoShowcase

Michal Moczulski

July 09, 2015
Tweet

More Decks by Michal Moczulski

Other Decks in Programming

Transcript

  1. AGENDA • Sample App • Adding Espresso to project •

    Sample Test • View Matchers and View Actions • Mocking Intents • Synchronization
  2. ESPRESSO • UI Testing library for Android • Concise syntax

    • Handling synchronization • Fast • Developed by Google
  3. ADDING TO PROJECT dependencies { androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2' androidTestCompile 'com.android.support.test:runner:0.3' androidTestCompile

    'com.android.support.test.espresso:espresso-intents:2.2' /** * AccessibilityChecks * CountingIdlingResource * DrawerActions * DrawerMatchers * PickerActions (Time and Date picker) * RecyclerViewActions */ androidTestCompile 'com.android.support.test.espresso:espresso-contrib:2.2' } https://github.com/mrmike/EspressoShowcase/blob/master/app/build.gradle
  4. SAMPLE TEST @RunWith(AndroidJUnit4.class) public class SampleTest { @Rule public ActivityTestRule<MainActivity>

    mRule = new ActivityTestRule<>(MainActivity.class); @Test public void testTitle() { onView(withText(R.string.title)).check(matches(isDisplayed())); } @Test public void testAllOf() { onView(allOf(withHint(R.string.hint), withId(R.id.text_input), hasVisibleCursor())).check(matches(isDisplayed())); } @Test public void testAnyOf() { onView(anyOf(withHint(R.string.hint), withId(R.id.title))) .check(matches(isDisplayed())); } } https://github.com/mrmike/EspressoShowcase/blob/master/app/src/androidTest/java/com/moczul/espresso/ showcase/SampleTest.java
  5. ERRORS android.support.test.espresso.AmbiguousViewMatcherException: '(with string from resource id: <2131099667>[hint] value: Enter

    input or with id: com.moczul.espresso.showcase:id/title)' matches multiple views in the hierarchy. Problem views are marked with '****MATCHES****' below. View Hierarchy: +>DecorView{id=-1, visibility=VISIBLE, width=1440, height=2560, has-focus=true, has-focusable=true, has-window- focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, child-count=3} | +->LinearLayout{id=-1, visibility=VISIBLE, width=1440, height=2392, has-focus=true, has-focusable=true, has-window- focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, child-count=2} | +-->ViewStub{id=16909171, visibility=GONE, width=0, height=0, has-focus=false, has-focusable=false, has-window- focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=true, is- selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0} | +-->FrameLayout{id=-1, visibility=VISIBLE, width=1440, height=2304, has-focus=true, has-focusable=true, has-window- focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=88.0, child-count=1} | +------>AppCompatTextView{id=2131492908, res-name=title, visibility=VISIBLE, width=1328, height=243, has- focus=false, has-focusable=false, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is- focusable=false, is-layout-requested=false, is-selected=false, root-is-layout-requested=false, has-input- connection=false, x=56.0, y=56.0, text=Espresso showcase, input-type=0, ime-target=false, has-links=false} ****MATCHES****
  6. VIEW MATCHER public static Matcher<View> hasVisibleCursor() { return new BoundedMatcher<View,

    EditText>(EditText.class) { @Override public void describeTo(Description description) { description.appendText(" has visible cursor."); } @Override protected boolean matchesSafely(EditText editText) { return editText.isCursorVisible(); } }; } https://github.com/mrmike/EspressoShowcase/blob/master/app/src/androidTest/ java/com/moczul/espresso/showcase/matchers/CustomMatchers.java
  7. VIEW ACTION public static ViewAction text(final String text) { return

    new ViewAction() { @Override public Matcher<View> getConstraints() { return ViewMatchers.isAssignableFrom(TextView.class); } @Override public String getDescription() { return "Set text in TextView"; } @Override public void perform(UiController uiController, View view) { ((TextView) view).setText(text); } }; } https://github.com/mrmike/EspressoShowcase/blob/master/app/src/androidTest/java/ com/moczul/espresso/showcase/actions/CustomActions.java
  8. EXTRAS IN LAUNCH INTENT @Rule public ActivityTestRule<MainActivity> mRule = new

    ActivityTestRule<>(MainActivity.class, false, false); @Before public void setUp() { Intent intent = new Intent(); intent.putExtra(MainActivity.EXTRA_TITLE, "ADG Poznan"); mRule.launchActivity(intent); } @Test public void testCustomTitle() { onView(withText("ADG Poznan")).check(matches(isDisplayed())); } https://github.com/mrmike/EspressoShowcase/blob/master/app/src/androidTest/ java/com/moczul/espresso/showcase/IntentExtrasTest.java
  9. SPEECH RECOGNITION 1/2 Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);

    startActivityForResult(intent, SPEECH_REQUEST_CODE); https://github.com/mrmike/EspressoShowcase/blob/master/app/src/main/java/com/ moczul/espresso/showcase/MainActivity.java
  10. SPEECH RECOGNITION 2/2 @Override protected void onActivityResult(int requestCode, int resultCode,

    Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == SPEECH_REQUEST_CODE && resultCode == RESULT_OK) { ArrayList<String> results = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS); mInput.setText(results.get(0)); } } https://github.com/mrmike/EspressoShowcase/blob/master/app/src/main/java/com/moczul/ espresso/showcase/MainActivity.java
  11. MOCKING INTENTS 1/2 @Before public void setUp() { Intent data

    = getResultIntent(); final Instrumentation.ActivityResult result = new Instrumentation.ActivityResult(Activity.RESULT_OK, data); intending(hasAction(RecognizerIntent.ACTION_RECOGNIZE_SPEECH)) .respondWith(result); } https://github.com/mrmike/EspressoShowcase/blob/master/app/src/androidTest/ java/com/moczul/espresso/showcase/MockedIntentTest.java
  12. MOCKING INTENTS 2/2 @Test public void testSpeechToRecognition(){ onView(withText(R.string.speech)).check(matches(isClickable())); onView(withText(R.string.speech)).perform(click()); intended(allOf(

    hasAction(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), hasExtraWithKey(RecognizerIntent.EXTRA_LANGUAGE_MODEL) )); onView(withId(R.id.text_input)).check(matches(hasText("ADG Poznan"))); } https://github.com/mrmike/EspressoShowcase/blob/master/app/src/androidTest/java/com/ moczul/espresso/showcase/MockedIntentTest.java
  13. SYNCHRONIZATION 1/5 • UI message queue • AsyncTask thread pool

    • IdlingResource • CountingIdlingResource
  14. SYNCHRONIZATION 2/5 mSubscription = observable.subscribeOn(RxScheduler.get()) /** * RxScheduler is based

    on Thread Pool executor * from PoolExecutor.java */ .observeOn(AndroidSchedulers.mainThread()) .subscribe(this); https://github.com/mrmike/EspressoShowcase/blob/master/app/src/main/java/com/ moczul/espresso/showcase/MainActivity.java
  15. SYNCHRONIZATION 3/5 public class SchedulerMonitor implements IdlingResource { @Override public

    boolean isIdleNow() { ... } @Override public String getName() { return "SchedulerMonitor"; } @Override public void registerIdleTransitionCallback(ResourceCallback resourceCallback) { mCallback = resourceCallback; // mCallback.onTransitionToIdle(); } } https://github.com/mrmike/EspressoShowcase/blob/master/app/src/androidTest/java/com/ moczul/espresso/showcase/monitor/SchedulerMonitor.java
  16. SYNCHRONIZATION 4/5 @Before public void setUp() { final SchedulerMonitor monitor

    = new SchedulerMonitor(PoolExecutor.get()); Espresso.registerIdlingResources(monitor); } https://github.com/mrmike/EspressoShowcase/blob/master/app/src/androidTest/java/com/ moczul/espresso/showcase/SyncDoneRightTest.java
  17. SYNCHRONIZATION 5/5 @Test public void syncTest() { onView(withId(R.id.text_input)).perform(text(“ADG-Poznan”)); // Start

    long running operation onView(withId(R.id.action)).perform(click()); onView(withId(R.id.output)).check(matches(hasText("ADG-Poznan"))); } https://github.com/mrmike/EspressoShowcase/blob/master/app/src/androidTest/java/com/ moczul/espresso/showcase/SyncDoneRightTest.java