Monday, December 07, 2009

Android Testing: External libraries

We are now applying all the techniques discussed in previous posts to test our android projects.

However, all the projects reviewed were so simple that no one used external libraries so we never faced the problem of using them from our tests.

In this example we are using a dummy external library we have called libdummy-0.0.1-SANPSHOT.jar. In our Activity we are creating an object from one of the library's classes.

When we write the tests and we need to resolve the references to that objects we may feel tempted to include libdummy in test project's external jars, but doing so

[2009-12-07 16:01:14 - TCTDD11Test]Launching instrumentation android.test.InstrumentationTestRunner on device emulator-5554
[2009-12-07 16:01:25 - TCTDD11Test]Test run failed: Class ref in pre-verified class resolved to unexpected implementation
[2009-12-07 16:01:25 - TCTDD11Test]Test run complete


we receive the previous error.

The solution is to export the external jar from the main project. Open project Properties and edit Java Build Path to include the external library



and export it to be contributed to dependent test project



and this will cause no problems running the tests.

Update: Android Compatibility library: Testing Fragments
The aforementioned solution can also be applied to the android-support.jar, the library needed to support Fragments in pre-Honeycomb versions. As before, this should be exported in the main project and used in tests.


Hope this helps.


Update: Using ant
Sometimes you may want to use ant to build your projects, for example when you are building using a Continuous Integration server like Hudson, Jenkins, Bamboo, CruiseControl or similar.
To achieve this goal the easiest way is to update the main and test projects using the android update project and android update test-project commands.
The missing step using this approach is the inclusion of the external libraries we used in our previous examples. In the libs directory of the main project include the used libraries. It's preferable to create symbolic links so they are automatically updated if you update the libraries by any mean.
This is how the libs directory will look like:


$ ls -l libs
total 16
lrwxr-xr-x 1 diego staff 82 1 Aug 18:13 android-support-v4.jar -> ../../../../opt/android-sdk/extras/android/compatibility/v4/android-support-v4.jar
lrwxr-xr-x 1 diego staff 46 1 Aug 18:29 libdummy-0.0.1-SNAPSHOT.jar -> ../../libdummy/lib/libdummy-0.0.1-SNAPSHOT.jar

Having done so, you will be able to create you application using ant debug and run the tests by ant run-tests and the libraries will be automatically included in the test project.

22 comments:

Juri Strumpflohner said...
This comment has been removed by the author.
Hambonious said...

Thanks! One thing to note is that if you still have a reference in your test project that it needs to be removed.

Anonymous said...

Thanks a lot for your hints. I could not find the solution easily, as Eclipse only reported 'Test run failed: java.lang.IllegalAccessError' and that's all.

Diego Torres Milano said...

Thanks for your comments. I'm glad to hear that it helped you.

Jonas Alves said...

Thank you very much.

Anon said...

You are a lifesaver man.

Diego Torres Milano said...

Thanks for your comments.

DeepThoughts said...

I did what you mentioned, but still hitting the same issue. :(

Diego Torres Milano said...

Start with a very simple project, a Helloworld like project would help, follow the steps, post any problem.

TimBav said...

I build with Maven, and my app uses Guava:

<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>r09</version>
<scope>compile</scope>
</dependency>

My instrumentation tests kept throwing this exception in the <clinit> phase of a class the code under test depended on:

Caused by: java.lang.IllegalAccessError: cross-loader access from pre-verified class
at dalvik.system.DexFile.defineClass(Native Method)
at dalvik.system.DexFile.loadClass(DexFile.java:193)
at dalvik.system.PathClassLoader.findClass(PathClassLoader.java:203)
at java.lang.ClassLoader.loadClass(ClassLoader.java:573)
at java.lang.ClassLoader.loadClass(ClassLoader.java:532)

I was able to resolve this by either adding Guava as an <exclusion>, or -- what is probably the correct solution -- by declaring the APK dependency with <scope>provided</scope> instead of the default scope that most Android test examples suggest.

jlitzingInthinc said...
This comment has been removed by the author.
OpenYourDream said...

Wow, this is the exact solution that I have been found. Thanks a lot.

David Wong said...

+1 thanks for this solution for eclipse, but is there also a solution for building with ant as well?
e.g.
App project, library project and test project all depend on an external jar.
The test project includes the external jar in build.properties with the line jar.libs.dir=... so that it can compile.
Everything builds OK, but when trying to run a test case with adb, then will also then get the IllegalAccessError Class ref in pre-verified class resolved to unexpected implementation.

Diego Torres Milano said...

Thanks all for your comments.
I updated the post to include an example of building android projects with ant and including external libraries that may be used in the tests as well.

Sachin said...

Thanks a lot Diego Torres Milano. You helped me a lot.

marc1s said...
This comment has been removed by the author.
marc1s said...

TimBav, you are my hero of the day. Indeed, your hint to use a dependency with provided solved my NoClassDefFoundError in a maven test project, testing a fragment activity from the compatibility support library.

To be more clear, in case someone else bumps into this:

In the main project, I added the dependency as follows:

<dependency>
<groupId>android.support</groupId>
<artifactId>compatibility-v4</artifactId>
</dependency>



and in the test project, like this:


<dependency>
<groupId>android.support</groupId>
<artifactId>compatibility-v4</artifactId>
<scope>provided</scope>
</dependency>

Thanks!

Lorie said...

I have the same problem as Timbav.
The solution to declare guava dependency as provided works, but you cannot use guava classes in the test project.
Is there a good tutorial to handle this problem with maven?

Diego Torres Milano said...

I haven't tried to use guava in the test project. I will do it and see if there could be any solution or workaround.
As a last resort, remember that building with ant works in these cases.

Qiao Feng said...

Hi,

I try to use ant to run Android JUnit test with android-support-v4.jar since I am using FragmentActivity.
I add a link with command "ln ../testedProject/libs/android-support-v4.jar ./libs/android-support-v4.jar" in my test project. The build can be passed but there is still error when running the test case as below:

[exec] android.test.suitebuilder.TestSuiteBuilder$FailedToCreateTests:
[exec] Error in testSuiteConstructionFailed:
[exec] java.lang.RuntimeException: Exception during suite construction
[exec] at android.test.suitebuilder.TestSuiteBuilder$FailedToCreateTests.testSuiteConstructionFailed(TestSuiteBuilder.java:239)
[exec] at java.lang.reflect.Method.invokeNative(Native Method)
[exec] at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:169)
[exec] at android.test.AndroidTestRunner.runTest(AndroidTestRunner.java:154)
[exec] at android.test.InstrumentationTestRunner.onStart(InstrumentationTestRunner.java:520)
[exec] at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1447)
[exec] Caused by: java.lang.reflect.InvocationTargetException
[exec] at com.test.QuestionListActivityTest.(QuestionListActivityTest.java:17)
[exec] at java.lang.reflect.Constructor.constructNative(Native Method)
[exec] at java.lang.reflect.Constructor.newInstance(Constructor.java:446)
[exec] at android.test.suitebuilder.TestMethod.instantiateTest(TestMethod.java:87)
[exec] at android.test.suitebuilder.TestMethod.createTest(TestMethod.java:73)
[exec] at android.test.suitebuilder.TestSuiteBuilder.addTest(TestSuiteBuilder.java:263)
[exec] at android.test.suitebuilder.TestSuiteBuilder.build(TestSuiteBuilder.java:185)
[exec] at android.test.InstrumentationTestRunner.onCreate(InstrumentationTestRunner.java:373)
[exec] at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4218)
[exec] at android.app.ActivityThread.access$3000(ActivityThread.java:125)
[exec] at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2071)
[exec] at android.os.Handler.dispatchMessage(Handler.java:99)
[exec] at android.os.Looper.loop(Looper.java:123)
[exec] at android.app.ActivityThread.main(ActivityThread.java:4627)
[exec] at java.lang.reflect.Method.invokeNative(Native Method)
[exec] at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)
[exec] at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)
[exec] at dalvik.system.NativeStart.main(Native Method)
[exec] Caused by: java.lang.NoClassDefFoundError: com.activity.QuestionListActivity

The test can be run with Eclipse successfully, but it will always failed as above with ant command.
Does anybody know how to solve it?

Qiao Feng said...
This comment has been removed by the author.
Diego Torres Milano said...

@Qiao Feng,
I can't reproduce your problem.
For me it's working correctly either from Eclipse or from ant.

You should not have android-support library in your test project's libs directory. Perhaps this is the reason why it's failing.