Testing Talkback in isolation with Espresso

Android engineer. Basketball lover. Software craftsman at Novoda

You can easily detect if TalkBack is enabled when binding views. You can then change their behaviour to make interaction with elements more natural. This blogpost explains how to test this functionality.

The biggest problem with testing custom behaviour with TalkBack is time. You turn it on, navigate to the app, ensure navigation between elements works as expected, check elements have the correct content descriptions and usage hints (with different data sets) and finally turn it off. It can be difficult to unit test this behaviour as it relies heavily on Android framework classes. You could also use Espresso to run tests on real devices, but the problem still remains with the setup and running of the tests.

We noticed this problem in some of our projects, so we wrote a simple library that is able to turn TalkBack on, run Espresso tests and turn it off after each test.

System under test

So let's imagine you have a TweetView with multiple actions represented by different buttons. The TweetView hides these buttons when TalkBack is enabled, but the user can choose any of the actions from a dialog, which appears after clicking on the view.

public void bind(final Tweet tweet, final TweetActions tweetActions) {  
    authorTextView.setText(tweet.author);
    summaryTextView.setText(tweet.summary);
    setContentDescription(tweet.author + ", " + tweet.summary);

    if (accessibilityServices.isSpokenFeedbackEnabled()) {
        buttonsView.setVisibility(GONE);

         setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                createActionsDialog(tweetActions);
            }
        });

        setOnLongClickListener(
            detailsAction.asLongClickListener()
        );
    ...
}

On top of that, you can add custom click and long click labels.

usageHintsAccessibilityDelegate.setClickLabel(  
    R.string.tweet_click_usage_hint
);
usageHintsAccessibilityDelegate.setLongClickLabel(  
    R.string.tweet_long_click_usage_hint
);

ViewCompat.setAccessibilityDelegate(  
    this, 
    usageHintsAccessibilityDelegate
);

It’s important that you test that everything behaves as expected when TalkBack is disabled. It’s worth reading our previous blogpost on testing views in isolation with Espresso to help you setup the library and add your base tests.

When TalkBack is enabled, you can test:

Addition setup for TalkBack

Toggling TalkBack state requires the WRITE_SECURE_SETTINGS permission, so you need to grant it to your app. First, you need to install the app, then you can run this command:

adb shell pm grant $PACKAGE_NAME android.permission.WRITE_SECURE_SETTINGS  

Just remember to change $PACKAGE_NAME to your own.

Once the app is installed and the permission is granted you can easily toggle TalkBack in two ways:

$ adb shell am start -a "com.novoda.espresso.ENABLE_TALKBACK"
$ adb shell am start -a "com.novoda.espresso.DISABLE_TALKBACK"
Intent intent = new Intent("com.novoda.espresso.DISABLE_TALKBACK"); // or ENABLE_TALKBACK  
context.startActivity(intent);  

Tests

So let's create a TweetViewTalkBackTest in androidTest with TalkBackViewTestRule.

@RunWith(AndroidJUnit4.class)
@LargeTest
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class TweetViewTalkBackTest {

    @Rule
    public ViewTestRule<TweetView> viewTestRule = 
        new TalkBackViewTestRule<>(R.layout.view_tweet_view);

This first test will check if buttons are actually hidden.

@Test
public void whenBoundTweetView_thenButtonsAreHidden() {  
    bindTweetView();

    onView(withId(R.id.tweet_buttons_view))
        .check(matches(withEffectiveVisibility(Visibility.GONE)));
}

private void bindTweetView( {  
    viewTestRule.bindViewUsing(new ViewTestRule.Binder<TweetView>() {
        @Override
        public void bind(TweetView view) {
            view.bind(TEST_TWEET, tweetActions);
        }
    });
}

Then you can check if clicking on a View displays the ActionDialog and has all required actions.

@Test
public void whenClicking_thenDisplaysDialogWithAllActions() {  
    onView(withClassName(is(TweetView.class.getName())))
        .perform(click());

    checkViewsWithTextDisplayed(
        R.string.tweet_action_details,
        R.string.tweet_action_reply,
        R.string.tweet_action_retweet,
        R.string.tweet_action_like
    );
}

private void checkViewsWithTextDisplayed(int... ids) {  
    for (int id : ids) {
        onView(withText(id))
            .check(matches(isDisplayed()));
    }

After that, you can make sure that clicking particular actions triggers the correct callback.

@Test
public void whenClickingDetails_ThenOpensDetails() {  
    displayActionDialog();

    onView(withText(R.string.tweet_action_details))
        .perform(click());

    verify(tweetActions).onDetails(TEST_TWEET);
}

Finally, you can test if the content description and usage hints have been set up the way you want them:

@Test
public void whenBoundTweetView_thenHasCorrectContentDescription() {  
    bindTweetView();

    onView(withClassName(is(TweetView.class.getName())))
            .check(matches(withContentDescription(
                TWEET_AUTHOR + ", " + TWEET_SUMMARY)));
}

@Test
public void whenBoundTweetView_thenHasCustomUsageHint() {  
    bindTweetView();

    onView(withClassName(is(TweetView.class.getName())))
        .check(matches(withUsageHintOnClick("See actions")));
}

@Test
public void whenBoundTweetView_thenHasLongClickCustomUsageHint() {  
    bindTweetView();

    onView(withClassName(is(TweetView.class.getName())))
        .check(matches(withUsageHintOnLongClick("Open details")));
}

And that's it 🎉 . Our QA team loves this tool, so let us know how it works for you!

About Novoda

We plan, design, and develop the world’s most desirable software products. Our team’s expertise helps brands like Sony, Motorola, Tesco, Channel4, BBC, and News Corp build fully customized Android devices or simply make their mobile experiences the best on the market. Since 2008, our full in-house teams work from London, Liverpool, Berlin, Barcelona, and NYC.

Let’s get in contact