Testing elapsed time with Espresso IdlingResource


The original blog is Espresso: Elapsed time.

When do you need to write the test that needs to wait, you need to use IdlingResource. Because Espresso does not wait for you.

Create simple elapsed time application, with one button and one text view. When to clicking the start button, the number will run until click stop button.

screenshot-2016-07-28-23-53-34

 

We will write the test to click the start button, wait and click stop. Let’s go!

Setup
Add instrumentation runner and espresso dependencies.

    defaultConfig {
        ...
        testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner'
    }
dependencies {
    androidTestCompile ('com.android.support.test:runner:0.5')
    androidTestCompile ('com.android.support.test:rules:0.5')
    androidTestCompile ('com.android.support.test.espresso:espresso-core:2.2.2') 
    androidTestCompile ('com.android.support.test.espresso:espresso-idling-resource:2.2.2') 
}

Create Idling Resource

public class ElapsedTimeIdlingResource implements IdlingResource {
    private final long startTime;
    private final long waitingTime;
    private ResourceCallback resourceCallback;

    public ElapsedTimeIdlingResource(long waitingTime) {
        this.startTime = System.currentTimeMillis();
        this.waitingTime = waitingTime;
    }

    @Override
    public String getName() {
        return ElapsedTimeIdlingResource.class.getName() + ":" + waitingTime;
    }

    @Override
    public boolean isIdleNow() {
        long elapsed = System.currentTimeMillis() - startTime;
        boolean idle = (elapsed >= waitingTime);
        if (idle) {
            resourceCallback.onTransitionToIdle();
        }
        return idle;
    }

    @Override
    public void registerIdleTransitionCallback(ResourceCallback callback) {
        resourceCallback = callback;
    }
}

Create Custom Matcher
The IdlingResource is not waiting on exactly time when we specific. So, we need to check it waiting for more than we specific time instead.

public class MoreThanMatcher {

    public static Matcher moreThan(final int expectedValue) {
        return new BoundedMatcher<View, TextView>(TextView.class) {
            @Override
            protected boolean matchesSafely(TextView textView) {
                float actualValue = Float.valueOf(textView.getText().toString());
                return (int) actualValue == expectedValue;
            }

            @Override
            public void describeTo(Description description) {
                description.appendText("more than: " + expectedValue);
            }
        };
    }
}

Write Test
Create test class and add annotation. Make sure that created it in androidTest, not test package.

    @RunWith(AndroidJUnit4.class)
    public class MainActivityTest {

    }

Add activity test rule.

    @Rule
    public ActivityTestRule activityTestRule = new ActivityTestRule<>(MainActivity.class);

Reset timeout before run each test. Because we need to modify timeout policy for each test.

    @Before
    public void resetTimeout() {
        IdlingPolicies.setMasterPolicyTimeout(60, TimeUnit.SECONDS);
        IdlingPolicies.setIdlingResourceTimeout(26, TimeUnit.SECONDS);
    }

It will default time out of Espresso, if the app has been idle is 60 seconds and it will default time out of IdlingResource, if app has been idle is 26 seconds.

    public final class IdlingPolicies {

        private IdlingPolicies() { }

        private static volatile IdlingPolicy masterIdlingPolicy = new IdlingPolicy.Builder()
            .withIdlingTimeout(60)
            .withIdlingTimeoutUnit(TimeUnit.SECONDS)
            .throwAppNotIdleException()
            .build();


        private static volatile IdlingPolicy dynamicIdlingResourceErrorPolicy = new IdlingPolicy.Builder()
            .withIdlingTimeout(26)
            .withIdlingTimeoutUnit(TimeUnit.SECONDS)
            .throwIdlingResourceTimeoutException()
            .build();

        ...
    }

Write waitFor method to set new timeout policy for use the test is not timeout before it done. So, we set the new timeout to double of waitingTime. Then register own IdlingResource to Espresso and make sure that unregister it after the test is done.

    private void waitFor(int waitingTime) {

        onView(withId(R.id.toggle_button))
                .check(matches(withText(R.string.start)))
                .perform(click());

        // Make sure Espresso does not time out
        IdlingPolicies.setMasterPolicyTimeout(waitingTime * 2, TimeUnit.SECONDS);
        IdlingPolicies.setIdlingResourceTimeout(waitingTime * 2, TimeUnit.SECONDS);

        IdlingResource idlingResource = new ElapsedTimeIdlingResource(DateUtils.SECOND_IN_MILLIS * waitingTime);
        Espresso.registerIdlingResources(idlingResource);

        onView(withId(R.id.toggle_button))
                .check(matches(withText(R.string.stop)))
                .perform(click());

        onView(withId(R.id.elapsed_Time))
                .check(matches(moreThan(waitingTime)));

        Espresso.unregisterIdlingResources(idlingResource);
    }
}

Then use it to write the test.

    @Test
    public void waitFor5Seconds() {
        waitFor(5);
    }

    @Test
    public void waitFor65Seconds() {
        waitFor(65);
    }

Here the result.
Screenshot 2016-07-30 15.55.05

Source code: GitHub


One thought on “Testing elapsed time with Espresso IdlingResource

  1. Dude, I donĀ“t have anything else to say, but thanks a lot, you have helped me more than any other blog or tutorial about this topic, THANKS

Leave a Reply

Your email address will not be published.