Posts

About

Clean Android Networking Code Part 2 - Unit Testing

July 07, 2015

Introduction

In Part 1 a cleaner way of implementing Android networking code with Volley and EventBus was explored. This post will build upon Part 1 by showing ways to test the new and improved clean code at the unit level. These tests will utilize a popular Android testing frameworks called Espresso. If you are unfamiliar with it then examples and tutorials can be found here. The code that tests will be added to in this post can be found on Github

Why Testing Is Important

Tests give you confidence that something is functioning properly. They also allow you to easily refactor code. After any change, they can be ran and in seconds giving you instant feedback on if you broke something or not. As Uncle Bob puts it in Clean Code,

Test code is just as important as production code. It is not a second class citizen. It requires thought, design, and care.

Unit Tests

Unit tests focus on testing a single unit and usually mock out calls to external ones. In the code there are three main units which just so happen to correspond to the three classes. GoogleStringRequest, and WebRequestQueue don’t really make sense to unit test since there is very little logic going on in them. For the most part, they delegate all their work to other classes or libraries. MainActivity is worth testing since it must handle string and error response events posted to the event bus. It is much easier to do this at the unit level. This is because no mocking of web servers or requests will have to take place. Instead a unit level test using Espresso and Android’s ActivityTestRule can be created to post events for testing.

Dependencies

Add Espresso dependency:

androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2'
androidTestCompile 'com.android.support.test:runner:0.2'
androidTestCompile 'com.android.support.test:rules:0.2'

Change test runner in default config in build.gradle

defaultConfig {
    ...
    testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}

You may also have to add the following to the android section of the build.gradle file if you receive an error during build.

packagingOptions {
    exclude 'LICENSE.txt'
}

Creating the test

Now a unit test can be created extending ActivityTestRule which will handle the setup of the activity given the class of the activity and and intent to start it with.

MainActivityUnitTest.java

public class MainActivityUnitTest extends ActivityTestRule<MainActivity> {

    public MainActivityUnitTest() {
        super(MainActivity.class);
    }

    @Before
    public void setUp() {
        launchActivity(new Intent());
    }
}

Stubbing web requests.

If you look at the MainActivity class it is creating and executing a new GoogleStringRequest. Since this is a unit test, this is something we will want to stub. This can be accomplished by modifying WebRequestQueue to allow setting its instance and using Mockito to set the instance to a mocked request queue. However this is a hack and not very clean since MainActivity does not know anything about WebRequestQueue. This will be remedied in Part 3.

Add Mockito dependency:

androidTestCompile 'org.mockito:mockito-core:1.10.19'
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'

Addition to WebRequestQueue:

public static void setInstance(WebRequestQueue requestQueue) {
    webRequestQueue = requestQueue;
}

Addition to MainActivityUnitTest setup to stub requests:

@Before
public void setUp() {
    WebRequestQueue requestQueue = mock(WebRequestQueue.class);
    doNothing().when(requestQueue).addToQueue(any(Request.class), anyString());
    WebRequestQueue.setInstance(requestQueue);
    launchActivity(new Intent());
}

Creating tests

The first test we will write will verify given a string response, the TextView’s text is set to that response. This can be accomplished by posting a string to our event bus in our test. Our test code does not run on the UI thread, so we must explicitly do this when posting to the event bus in test code. A simple helper class can be created to do this.

public class EventBusHelper {
    public ActivityTestRule rule;

    public EventBusHelper(ActivityTestRule rule) {
        this.rule = rule;
    }

    public void post(final Object o) {
        try {
            rule.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    EventBus.getDefault().post(o);
                }
            });
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }
}

This helper can then be created in the test setup and used to write the first test.

MainActivityUnitTest.java

    EventBusHelper eventBusHelper;

    @Before
    public void setUp() {
        ...
        eventBusHelper = new EventBusHelper(this);
    }

    @Test
    public void handleStringResponse() {
        String response = "test";
        eventBusHelper.post(response);
        onView(withId(R.id.text)).check(matches(withText(response)));
    }

The second test for verifying handling a volley error is just as simple.

    @Test
    public void handleVolleyError() {
        String expected = "That didn't work!";
        eventBusHelper.post(new VolleyError());
        onView(withId(R.id.text)).check(matches(withText(expected)));
    }

Conclusion

As you can see it is very simple to test handling the response of network code at the unit level like this. The full example can be found on Github


Written by Jacob Oakes
I am a software architect who enjoys learning new things, clean code, and automated tests.