Month: October 2014

How to use Robolectric and still make real HTTP requests

Recently, I have been writing some unit tests for my application using Robolectric. The framework and plugin has allowed me to write some plain unit tests using Mockito which is great, however, today I came across a feature that I didn’t want to take advantage of.

I was testing a class that is responsible for making REST HTTP requests to a server and for this test I wanted to get real responses. However, when running the tests in Android Studio everything was fine, but as soon as I ran it from the command line using gradle I got the following error.


java.lang.RuntimeException: Unexpected call to execute, no pending responses are available. See Robolectric.addPendingResponse(). Request was: GET http://127.0.0.1/rest/543e7ee26c3f52cce18c365f
	at org.robolectric.tester.org.apache.http.FakeHttpLayer.emulateRequest(FakeHttpLayer.java:127)
	at org.robolectric.shadows.ShadowDefaultRequestDirector.execute(ShadowDefaultRequestDirector.java:162)
	at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java)
	at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:555)
	at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:487)
	at org.springframework.http.client.HttpComponentsClientHttpRequest.executeInternal(HttpComponentsClientHttpRequest.java:83)
	at org.springframework.http.client.AbstractBufferingClientHttpRequest.executeInternal(AbstractBufferingClientHttpRequest.java:46)
	at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:63)
	at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:476)
	at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:439)
	at org.springframework.web.client.RestTemplate.getForEntity(RestTemplate.java:259)
	at com.lsc.spear.services.REST.tasks.GetAssetByIdRequest.doInBackground(GetAssetByIdRequest.java:70...

This was down to a feature in Robolectric that allows developers to test their code without making real HTTP requests. See their blog article on this here

However, in my situation I wanted to make the real requests. You can turn this feature off using the method call.

Robolectric.getFakeHttpLayer().interceptHttpRequests(false);

I put it in my @Before annotated method so it was configured for all my tests.

How to generate JavaDoc for Android using Gradle

So, today I’ve been trying to get JavaDoc produced and I’ve come across a number of issues. Basically, there doesn’t seem to be a single place that just says do this. There are many stackoverflow answers, some work, some don’t then if you are running JDK 1.8 then there are other issues. So, I’ll show below how to generate JavaDoc, how to call it from Gradle (because I’m new to this and it wasn’t that obvious) and how to get around the JDK 1.8 issue.

Add the following configuration to your modules build.gradle file in your project. This is the one in the app folder if you are using a standard project setup.


android.applicationVariants.all { variant ->
    task("generate${variant.name.capitalize()}JavaDoc", type: Javadoc) {
        description "Generates Javadoc for $variant.name."
        source = variant.javaCompile.source
        ext.androidJar = "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar"
        classpath = files(variant.javaCompile.classpath.files) + files(ext.androidJar)
    }
}

To run this on the commandline then call ./gradlew generateDebugJavaDoc or ./gradlew generateReleaseJavaDoc

If you are running JDK 1.8 on your system you will get a whole pile of lint failures from your R.java file and since you can’t fix those then you have to work around the issue for now. This information was taken from http://blog.joda.org/2014/02/turning-off-doclint-in-jdk-8-javadoc.html

As you are using gradle it is simpler in my view to put this workaround in the gradle file so in the top-level project gradle file add the following.


if (JavaVersion.current().isJava8Compatible()) {
    allprojects {
        tasks.withType(Javadoc) {
            options.addStringOption('Xdoclint:none', '-quiet')
        }
    }
}

Conflict with Robolectric and SlideUpPanelLayout

The other day I was trying to integrate Robolectric with my project that uses the SlideUpPanelLayout. If I removed either Robolectric from the test build or SlideUpPanelLayout from my project it fixed the issue (temporarily, because I needed both) When using Robolectric and SlideUpPanelLayout together I get the following error.

Test failed to run to completion. Reason: ‘Instrumentation run failed due to 'java.lang.IllegalAccessError''. Check device logcat for details
Test running failed: Instrumentation run failed due to 'java.lang.IllegalAccessError'

This error is down to a Gradle configuration error. The reason is that there are dependencies declared multiple times in the build and one of them needs to be excluded. The challenge in this situation is knowing which dependency it is.

To work this out look at the exception which in this case ended with

Caused by: java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation
at com.sothree.slidinguppanel.ViewDragHelper.<init>(ViewDragHelper.java:392)
at com.sothree.slidinguppanel.ViewDragHelper.create(ViewDragHelper.java:348)
at com.sothree.slidinguppanel.ViewDragHelper.create(ViewDragHelper.java:361)
at com.sothree.slidinguppanel.SlidingUpPanelLayout.<init>(SlidingUpPanelLayout.java:334)
at com.sothree.slidinguppanel.SlidingUpPanelLayout.<init>(SlidingUpPanelLayout.java:263)
... 26 more

ViewDragHelper was the culprit so if in your IDE (Android Studio) open the search for class name (Command-O in OS X) and enter ViewDragHelper. You will see that there are multiple Jars that are returned. You will need to exclude the one that you don’t want.

In this situation we had the android-19 sdk, support-v4-19 and support-v4-20 and library-2.0.1 (the slideuppanellayout library) to solve this I needed to exclude support-v4.

So in the build gradle file for the project where I included robolectric I needed to add the following to the list of excludes.

exclude module: 'support-v4'

This solved the problem!

NullPointerException when using Robolectric and Google Play Services

Today whilst writing a unit test for my Android application I had issues using Robolectric in my application. After a lot of hunting around and narrowing down the problem I found that it is was because of the declaration of Google Play Services in my AndroidManifest.xml file.

The error I got was

java.lang.RuntimeException: java.lang.NullPointerException
at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:240)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
at org.robolectric.RobolectricTestRunner$1.evaluate(RobolectricTestRunner.java:177)
at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.runTestClass(JUnitTestClassExecuter.java:86)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.execute(JUnitTestClassExecuter.java:49)
at org.gradle.api.internal.tasks.testing.junit.JUnitTestClassProcessor.processTestClass(JUnitTestClassProcessor.java:69)
at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:48)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.messaging.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32)
at org.gradle.messaging.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93)
at com.sun.proxy.$Proxy2.processTestClass(Unknown Source)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:105)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35)
at org.gradle.messaging.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.messaging.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:355)
at org.gradle.internal.concurrent.DefaultExecutorFactory$StoppableExecutorImpl$1.run(DefaultExecutorFactory.java:64)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.NullPointerException
at org.robolectric.AndroidManifest$MetaData.init(AndroidManifest.java:698)
at org.robolectric.AndroidManifest.initMetaData(AndroidManifest.java:364)
at org.robolectric.res.builder.RobolectricPackageManager.addManifest(RobolectricPackageManager.java:364)
at org.robolectric.internal.ParallelUniverse.setUpApplicationState(ParallelUniverse.java:77)
at org.robolectric.RobolectricTestRunner.setUpApplicationState(RobolectricTestRunner.java:430)
at org.robolectric.RobolectricTestRunner$2.evaluate(RobolectricTestRunner.java:236)
... 35 more

The key parts of this stack trace being the NullPointerException and the fact it was coming from AndroidManifest.java. The cause of this issue was down to the fact in the AndroidManifest.xml I have this declaration to include Google Maps.


<meta-data android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />

The part that is causing issues is the @integer/google_play_services_version and it is something to do with the fact the google_play_services_version is embedded in the Jar and it cannot be processed at the point it needs to be.

How to fix this?
To fix this issue you will can hard code it but that is a very hacky solution. It would be better to define this value locally but still point it at the correct value, for some reason this works.

So, in the values section of your project create a new resource file and call it something like values.xml.

In that file add the following code


<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="gms_version">@integer/google_play_services_version</integer>
</resources>

Then in your AndroidManifest.xml file where @integer/google_play_services_version was previously declared then put @integer/gms_version

This will work around the issue which is open here.

Android testing is hard, which is probably why people don’t do it!

So, over the past few weeks I have been trying to automate the testing of my application. Every time I attempt to do something I come across a pretty major hurdle.

I am currently producing an application on the Android platform that includes a Google v2 Map View, Umano / SlidingUpPanelLayout, Sensors, Camera, REST calls etc. So, far my experience of test frameworks out there (Espresso, Robolectric, Robotium) all seem to work for standard apps with list views, text fields, launching new activities but when we start to use Maps etc. I come to a grinding halt.

Writing automated tests isn’t easy the first time round but Android seems to be harder than writing plain tests for Java Web Applications. It makes me question whether it is worth the effort!

My experience with Maps so far have been one of continued pain. The first challenge is getting the Maps to work on an emulator as by default this isn’t supported. I have had to jump through many hoops by downloading the APKs (com.android.vending-20140218.apk & com.google.android.gms-20140218.apk) from various sources to get the basic maps working. Then when this is installed I get continuous error messages popping up. Genymotion looks like a better solution and has a very good emulator (which costs) but even that isn’t pain free.

Testing anything with the mapping again is challenging. My application places markers on the map and I would like to have a test that taps on these markers however, just isn’t possible. I have had to work around this by keeping track of the markers in the test code and calling the onMarkerClick method using a cached Marker object!

Espresso in general has been a great framework to write a majority of automated tests but it does rely heavily on writing custom matchers and actions for controls like map views etc.

In summary, Android testing is tough and anyone that writes Android tests deserves a medal!

Solution for Test running failed: Unable to find instrumentation info for: ComponentInfo{…./com.google.android.apps.common.testing.testrunner.GoogleInstrumentationTestRunner}

Whilst using Espresso I have come across an issue a number of times and each time it takes me a few minutes to remember what the solution is. So, I thought I should put it down here!

Solution

Ensure that both your build.gradle in the app folder and the run configuration in Android Studio need to declare the com.google.android.apps.common.testing.testrunner.GoogleInstrumentationTestRunner.

In the build.gradle file in your app folder you need to add the line

testInstrumentationRunner “com.google.android.apps.common.testing.testrunner.GoogleInstrumentationTestRunner”

In your android studio run configuration you will need to add com.google.android.apps.common.testing.testrunner.GoogleInstrumentationTestRunner to the Specific Instrumentation Test Runner field.

Explanation

The com.google.android.apps.common.testing.testrunner.GoogleInstrumentationTestRunner is a requirement when using Espresso and although the error message (Unable to find instrumentation info for: ComponentInfo{…./com.google.android.apps.common.testing.testrunner.GoogleInstrumentationTestRunner}) is highlighting that the Activity might not have instrumentation set you’ll actually find that it is when you run the command 

 adb shell pm list instrumentation

I am using Android Studio for my development and therefore a lot of the solutions out there talk about modifying the test folders AndroidManifest.xml file. When using Android Studio and the Gradle build system this isn’t needed because the test AndroidManifest.xml is auto generated. As long as you specify the new Google Instrumented Test Runner in both of these locations the problem should resolve itself.

How to add a GoogleMap v2 MapView in your Android application

Today I wanted to convert my existing Android application to use the MapView View object rather than the fragment based object. This was partly to allow me to create some Espresso based matchers etc. but also I didn’t need the fragment approach for my application.

The first change was the the layout xml file. I needed to replace the fragment below.


<fragment
android:id="@+id/mapView"
android:name="com.google.android.gms.maps.MapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />

with the following mark up.


<com.google.android.gms.maps.MapView
android:id="@+id/mapView"
android:layout_width="match_parent"
android:layout_height="match_parent" />

The next task is mentioned in the JavaDoc for com.google.android.gms.maps.MapView it states that you have to pass through your Activity or Fragment lifecycle events through to the View object.

To start off with it would be a good idea in your onCreate method you initialise your member variable for your MapView object using the code below and then immediately call the MapView’s onCreate method. You can then use this variable in the other lifecycle methods.


@Override
public void onCreate() {
mapView = (MapView)findViewById(R.id.mapView);
mapView.onCreate(savedInstanceState);
}

Example of passing the activity lifecycle events through to the MapView.

@Override
public void onLowMemory() {
mapView.onLowMemory();
super.onLowMemory();
}

Initially I thought this was all that was needed however, on starting the application and then animating the position on the map using a CameraUpdateFactory I got the error java.lang.NullPointerException: CameraUpdateFactory is not initialized logcat exception a bit of googling found the stackoverflow article that said I needed to call MapsInitializer.initialize(this);

I added this to the onCreate method of my activity immediately after calling mapView.onCreate(savedInstance);


@Override
public void onCreate() {
mapView = (MapView)findViewById(R.id.mapView);
mapView.onCreate(savedInstanceState);
MapsInitializer.initialize(this);
}