Unit testing your Android Application using Groovy and Spock Framework

 

Unit Testing is a controversial topic. Everybody agrees it is a good thing and everybody should do it, but most projects I’ve seen do not, or not anymore. Which is a pity, since it can be – from a development standpoint – quite fun to write. I recently ran into the Spock framework, which lets you write unit tests using Groovy, and had to give it a try. Here’s a brief introduction.

 

So, to get started with groovy unit tests, we have to add the groovy android plugin to our top level gradle build file:

dependencies {
    classpath 'com.android.tools.build:gradle:1.2.3'
    classpath 'org.codehaus.groovy:gradle-groovy-android-plugin:0.3.6'
}

 

Note the dependency to an older android gradle build tools version. As of the time of this writing the groovy android plugin doesn’t seem to be fully compatible to the current build tools version.

Next, we’ll need to apply the plugin in our application level gradle build file and add our testing dependencies. Our gradle might then look something like this:

apply plugin: 'com.android.application'
apply plugin: 'groovyx.grooid.groovy-android'

android {
    // ...
    testOptions {
        unitTests.returnDefaultValues = true
    }
}

dependencies {
    // ...
    testCompile 'org.codehaus.groovy:groovy-all:2.4.3'
    testCompile 'org.spockframework:spock-core:1.0-groovy-2.4'
    testCompile 'cglib:cglib-nodep:3.1'
    testCompile 'org.objenesis:objenesis:2.2'
}

 

The latter two are are optional, and only needed when you want to create mocks for interfaceless classes and classes with no public default constructor. Another interesting thing to note is the unitTests.returnDefaultValues = true testOption. The Android framework usually ships with stubs only, that aren’t implemented when not running on a device or an emulator. This option lets you use those methods in our unit tests running on the JVM with default values. While this option is very nice to have and tests can be run via command line, running tests using those methods in question within Android studio won’t be possible even with this option turned on. Hopefully support for this will be added soon.

The Groovy support seems to be rather good within Android studio without the need to install any further plugins. There is syntax highlightning, code completion and debugging support – which is not all that common when it comes to scripting languages. So that’s definitely a plus. The unit tests go in src/test/groovy/{your_package} and we will have to switch the Build variants Test artifacts from Android Instrumentation tests to Unit tests in order to see them in Android studio.

 

spockunittestsdemo-build-variant

 

For the sake of simplicity we’ll be looking at a simple JSON fetching and parsing example. We’ll be using Android Async to fetch data and Gson to parse them. The code under test will be this:

public void fetchContacts(final ContactProviderListener callback) {
    createClient().executeString(createGetRequest(), new AsyncHttpClient.StringCallback() {
        @Override
        public void onCompleted(Exception e, AsyncHttpResponse asyncHttpResponse, String result) {
            if (result != null) {
                try {
                    callback.onContacts(gson.fromJson(result, Contacts.class));
                } catch (JsonSyntaxException jse) {
                    callback.onError(jse);
                }
            } else {
                callback.onError(e);
            }
        }
    });
}

 

In this case we basically have three code paths we want to cover. The first one would be case where valid json has been fetched and parsed, in which case we want the callback to be invoked with the resulting contacts object. That looks like this in Groovy:

def "should provide contacts when valid json is given"() {
    given: "a valid contacts json"
    def contacts = new Contacts();
    contacts.setContacts([
            new Contact("john", "doe"),
            new Contact("jane", "doe"),
            new Contact("sheldon", "cooper"),
            new Contact("raj", "koothrappali"),
            new Contact("alex", "dunphy"),
            new Contact("mitchell", "pritchett"),
            new Contact("ross", "geller"),
            new Contact("chandler", "bing"),
    ])
    def contactsJson = new Gson().toJson(contacts)
    provider.createClient().executeString(_, _ as AsyncHttpClient.StringCallback) >> { _, stringCallback ->
        stringCallback.onCompleted(null, null, contactsJson)
    }

    when: "fetching contacts"
    provider.fetchContacts(callback)

    then: "contacts shall be given"
    1 * callback.onContacts(contacts)
}

 

As we can observe, the groovy test is very easy to read. The method name is a simple string that ideally describes what we want to test. The test itself is structured in blocks {setup, given, when, and, then, expect, where, etc.} which adds to its readability. In this case we set up our data and mock the executeString() in the  given block such that we return our desired json string to feed our method. The when block triggers our method under test and in our then block we test that our callback has been invoked exactly once and with the contacts object including all contacts that we set up earlier and expected to return.

For the second code path we test with malformed json and expect a JsonSyntaxException to be thrown – which we expect to be passed along when invoking onError on our callback listener:

def "should gracefully handle malformed contacts json"() {
    given: "a malformed contacts json"
    provider.createClient().executeString(_, _ as AsyncHttpClient.StringCallback) >> { _, stringCallback ->
        stringCallback.onCompleted(null, null, "{tis aint no valid jason, yo!}")
    }

    when: "fetching contacts"
    provider.fetchContacts(callback)

    then: "the corresponding exception will be passed along"
    1 * callback.onError({ it instanceof JsonSyntaxException })
}

 

And the last code path to cover is an exception thrown by the android async library we use:

def "should pass exception along in case http request failed"() {
    given: "an exception during communication attempt"
    def thrownException = new IOException("something went wrong")
    provider.createClient().executeString(_, _ as AsyncHttpClient.StringCallback) >> { _, stringCallback ->
        stringCallback.onCompleted(thrownException, null, null)
    }

    when: "fetching contacts"
    provider.fetchContacts(callback)

    then: "the exception will be passed along"
    1 * callback.onError({ it == thrownException })
}

 

Executing the groovy tests on command line as usual:

./gradlew testDebug

 

And that’s it. To make sure we have covered all code paths with unit tests we can use JaCoCo to generate a code coverage report for us. In our case it looks like this:

spockunittestsdemo-jacoco-1

spockunittestsdemo-jacoco-2

 

The sample code to this blog post is available on SpockUnitTestsDemo@Github. And be sure to check out the Spock framework documention, especially on the awesome @Unroll feature. Happy unit testing!

 

 

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *