About Espresso

  • Espresso is a simple and easy-to-use Android UI testing framework

  • Espresso consists of three basic components:

    • ViewMatchers – Matches the specified View in the current View hierarchy.
    • ViewActions – Perform certain actions of Views, such as click events.
    • ViewAssertions – Checks the state of some Views, such as whether they are displayed.
  • Examples of using Espresso

    OnView (ViewMatcher) //1. Perform (ViewAction) //2 Execute the View action. Check (ViewAssertion); / / 3. Validation of the ViewCopy the code

To prepare

Build. Gradle adds the following dependencies:

androidTestImplementation 'com.android.support.test.espresso:espresso-core:latest.version'
androidTestImplementation 'com.android.support.test:runner:latest.version'
androidTestImplementation 'com.android.support.test:rules:latest.version'
Copy the code

Step 2: Android.defaultConfig Add the following configuration

testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
Copy the code

Basic usage

Get the View

  • WithId way
onView(withId(R.id.my_view))
Copy the code
  • WithText way
onView(withText("Hello World!"))
Copy the code

Executing the View behavior

  • Click on the
onView(...) .perform(click());Copy the code
  • Text content entry
onView(...) .perform(typeText("Hello"), click());
Copy the code
  • ScrollTo sliding
onView(...) .perform(scrollTo(), click());Copy the code

Check the View

  • Verify the text content of the View
onView(...) .check(matches(withText("Hello!")));
Copy the code
  • Verify the display state of the View
onView(...) .check(matches(isDisplayed()));Copy the code

Example scenario:

Simple login scenario test

@RunWith(AndroidJUnit4.class)
public class LoginUITest {

    @Rule
    public ActivityTestRule<LoginActivity> rule=new ActivityTestRule<LoginActivity>(LogingActivity.class,true);

    @Test
    public void login(){
        //login
        onView(withId(R.id.userName)).perform(typeText("Jack"),closeSoftKeyboard());
        onView(withId(R.id.password)).perform(typeText("1234"),closeSoftKeyboard());
        onView(withText("Login")).perform(click()); //verify onView(withId(R.id.content)).check(matches(isDisplayed())); }}Copy the code

Advanced usage

1. Use IdlingResource

Usually, there are a lot of asynchronous tasks in our actual applications, such as network requests, image loading, etc. Espresso doesn’t know when your asynchronous task ends, so you need to use IdlingResource.

Note that if you are using AsyncTask or AsyncTaskCompat, Espresso already handles it and doesn’t need to do any extra processing.

Step 1: Add the dependency libraries

compile 'com.android.support.test.espresso:espresso-idling-resource:latest.version'
Copy the code

Step 2: Define an IdlingResource interface.

Public interface IdlingResource {/** * public String getName(); */ public Boolean isIdleNow(); / * * register an idle state transform ResourceCallback callback * / public void registerIdleTransitionCallback (ResourceCallback callback); /** * Public interface ResourceCallback {/** * When IdlingResource status changes to idle, Calling this method tells Espresso */ public void onTransitionToIdle(); }}Copy the code

The following uses an example to illustrate: Scenario: Suppose we need to test whether the user’s profile picture is displayed properly.

The Activity code

public class AvatarActivity extends AppCompatActivity{

    private ImageView mAvatar;

    public static SimpleIdlingResource sIdlingResource=new SimpleIdlingResource();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_idling_resource); mAvatar= (ImageView) findViewById(R.id.avatar); / / App began to busy, wait for a notice sIdlingResource. The increment (); Glide. With (this).load()"https://avatars2.githubusercontent.com/u/2297803?v=3&s=460").into(new GlideDrawableImageViewTarget(mAvatar) { @Override public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> animation) { super.onResourceReady(resource, animation); / / loading is completed, set the App into idle sIdlingResource. Decrement (); }}); }}Copy the code

SimpleIdlingResource code

public final class SimpleIdlingResource implements IdlingResource {

    private final AtomicInteger counter = new AtomicInteger(0);

    private volatile ResourceCallback resourceCallback;

    @Override
    public String getName() {
        return "SimpleIdlingResource"; } /** * if counter = 0, the state is idle */ @override public BooleanisIdleNow() {
        returncounter.get() == 0; } @Override public void registerIdleTransitionCallback(ResourceCallback resourceCallback) { this.resourceCallback = resourceCallback; } /** * counter increment method */ public voidincrement() { counter.getAndIncrement(); } /** *counter decrement method */ public voiddecrement() {
        int counterVal = counter.decrementAndGet();
        if(counterVal == 0) {// Execute onTransitionToIdle() to tell Espresso that it is idle.if (null != resourceCallback) {
                resourceCallback.onTransitionToIdle();
            }
        }

        if (counterVal < 0) {
            throw new IllegalArgumentException("Counter has been corrupted!"); }}}Copy the code

IdlingResourceTest code

@RunWith(AndroidJUnit4.class)
public class IdlingResourceTest {

    @Rule
    public ActivityTestRule<AvatarActivity> rule=new ActivityTestRule<>(AvatarActivity.class,true);

    @Before
    public void registerIdlingResource(){
        Espresso.registerIdlingResources(rule.getActivity().sIdlingResource);
    }

    @Test
    public void avatarDisplayed(){
        onView(withId(R.id.avatar)).check(matches(isDisplayed()));
    }

    @After
    public void unregisterIdlingResource() { Espresso.unregisterIdlingResources( rule.getActivity().sIdlingResource); }}Copy the code

In addition, Espresso provides a well-implemented CountingIdlingResource class, so if you don’t have specific requirements, just use CountingIdlingResource.

2. Create a custom Espresso Matcher

So far Espresso provides a method that can basically meet your testing needs, as shown below:

If you need to test a custom property in a custom View, you can create a custom Matcher

    public static Matcher<View> isMoved() {
        return new TypeSafeMatcher<View>() {
            @Override
            public void describeTo(Description description) {
                description.appendText("is moved");
            }

            @Override
            public boolean matchesSafely(View view) {
                return((CustomView)view).isMoved(); }}; }Copy the code

3. How to handle animation

  • System animation: To avoid animation threads running during pairsEspressoTo test the impact of the system animation, officials strongly recommend turning it off. As shown in the figure:

  • Custom animation: For custom animation, developers can use the following code to control the animation on and off.

This method listens for on-off events in system animations, which is a recommended practice, not just in Espresso testing.

    boolean animationEnabled=true;

    if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.JELLY_BEAN_MR1){
        try {
            if(Settings. Global. GetFloat (getContentResolver (), Settings. Global. ANIMATOR_DURATION_SCALE) = = 0.0 f) {animationEnabled =false; } } catch (Settings.SettingNotFoundException e) { e.printStackTrace(); }}Copy the code

4. ElegantIntentTest

  • Common way
    @Rule
    public ActivityTestRule<MainActivity> rule=new ActivityTestRule<MainActivity>(MainActivity.class){

        @Override
        protected Intent getActivityIntent() { Intent result=new Intent(...) ; result.putExtra(...)returnresult; }};Copy the code

Using the normal approach, all tests in the unit test class are activities launched based on the Intent, which is not flexible enough.

  • The elegant way
    @Rule
    public ActivityTestRule<MainActivity> rule=new ActivityTestRule<MainActivity>(MainActivity.class,true.false); // The third argument is whether to automatically start the Activity public voidmyTest(){ Intent result=new Intent(...) ; result.putExtra(...) ; rule.launchActivity(result); }Copy the code

5. Keep your tests independent

When testing UI using Espresso, you do not want to test network or remote services mocking, so you can do this by mocking Espresso Intent, Mockito for Mocking, dependency injection, and Dagger2. In short, try to separate things that don’t belong at the UI level.

For example, if you need a button click event to test, but the button click will jump to another Activity, but you don’t want to test another Activity, you can intercept the Intent.

First you need to add the IntentTest dependency library:

androidTestCompile 'com. Android. Support. Test. Espresso: espresso - the intents: 2.2.2'
Copy the code

InterceptIntentTest sample code

@RunWith(AndroidJUnit4.class)
public class InterceptIntentTest {

    @Rule
    public IntentsTestRule<MainActivity> rule=new IntentsTestRule<>(MainActivity.class);

    @Before
    public void stubCameraIntent() {/ / simulate a ActivityResult Instrumentation. ActivityResult result = createImageCaptureActivityResultStub (); ACTION_IMAGE_CAPTURE, And return the result result (hasAction(mediastore.action_image_capture).respondwith (result); } @Test public voidtakePhoto_cameraIsLaunched(){ onView(withId(R.id.button_take_photo)).perform(click()); intended(hasAction(MediaStore.ACTION_IMAGE_CAPTURE)); . } private Instrumentation.ActivityResultcreateImageCaptureActivityResultStub() {
        // Create the ActivityResult, with a null Intent since we do not want to return any data
        // back to the Activity.
        returnnew Instrumentation.ActivityResult(Activity.RESULT_OK, null); }}Copy the code

6. Avoid copying and pasting test code directly

Let’s take a simple example.

onData(allOf(is(instanceOf(Map.class)),hasEntry(equalTo("STR"),is("item:50")))).perform(click());
Copy the code

Above this code is matching eligible item in the list, click event, and executed test is normal also, this code is also being copied by other test methods used in, then, imagine, if your data source in the adapter into the cursor or other, so miserable, you need to modify a lot of places, obviously, This is not a proper CV warrior.

So we need to modify the previous line of code:

    @Test
    public void myTest(){
        onData(withItemContent("item:50")).perform(click());
    }

    public static Matcher<? extends Object> withItemContent(String expectedText) {
        ....
    }

Copy the code

It’s as simple as pulling out the parts that might change.

7. How to test the position of View

As shown in the figure:

8. Customize error logs

The default error log displays more information, as shown in the following figure:

If you want to display only the logs you care about, you can customize FailureHandler:

private static class CustomFailureHandler implements FailureHandler { private final FailureHandler delegate; public CustomFailureHandler(Context targetContext) { delegate = new DefaultFailureHandler(targetContext); } @Override public void handle(Throwable error, Matcher<View> viewMatcher) { try { delegate.handle(error, viewMatcher); } catch (NoMatchingViewException e) { throw new MySpecialException(e); }}}Copy the code
@Override
public void setUp() throws Exception {
    super.setUp();
    getActivity();
    setFailureHandler(new CustomFailureHandler(getInstrumentation()
                                              .getTargetContext()));
}
Copy the code

About me