Simple non-API Android JUnit test in Eclipse with android-maven-plugin?
Asked Answered
N

3

4

I run non-Android JUnit tests from within Eclipse every day. Today I wanted to test some of my Android library classes. Oh, the pain.

I have an Android library project using android-maven-plugin. I have source files in src/main/java and my (new) unit test in src/test/java. My POM has the appropriate JUnit dependencies and android-maven-plugin references.

Sometimes I create an Android Uri instance from a File. Sometimes I have an existing Java URI instance that I've created from a File which I then convert to a Uri. Since I trust neither Java nor Android with files and URIs (don't get me started on how Java mangles UNC paths in URIs, or how Java breaks the equals() contract in URIs), I wanted to create a simple unit test to create a temp file, create Uris from two different approaches, and make sure they come out equal.

So I make a little JUnit unit test like I'm used to, and try to run it in Eclipse using Ctrl+F11. Eclipse asks me if this is an "Android JUnit Test" or a "JUnit Test". Well, Android, obviously. So I choose the first option and get:

[2013-03-23 21:37:10 - mylib] ------------------------------
[2013-03-23 21:37:10 - mylib] Android Launch!
[2013-03-23 21:37:10 - mylib] adb is running normally.
[2013-03-23 21:37:10 - mylib] Could not find mylib.apk!

Hmmm... that wasn't very successful. So I delete the run configuration and try just "JUnit Test". Now I get a different dialog, asking me to select my preferred launcher, either "Android JUnit Test Launcher" or "Eclipse JUnit Test Launcher". It doesn't matter which I choose; I get:

Class not found com.example.MyUnitTest
java.lang.ClassNotFoundException: com.example.MyUnitTest
    at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:423)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:356)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.loadClass(RemoteTestRunner.java:693)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.loadClasses(RemoteTestRunner.java:429)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:452)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

I've read that with the android-maven-plugin I can run unit tests locally in Eclipse if they just use classes in the Android jar but don't make any API calls, which is what I'm doing here. So how do I pull that off?

Nosedive answered 24/3, 2013 at 0:44 Comment(4)
And worse than that, I can no longer even launch my application that depends on this library, as Eclipse/Android complains that there are multiple occurrences of JUnit/Hamcrest in the generated Dex files. So I have to rip out the JUnit dependencies from the POM and comment out any JUnit-related annotations or method calls from the test files.Nosedive
P.S. Sure enough, the reason I created this test to begin with was to see if Android and Java create the same types of URIs from files. Nope: one uses file:/// and the other uses file:/. Sheesh---why can't people get the simplest things working consistently?Nosedive
Just to draw your attention on this part of File.toURI() javadoc : "The URI is system dependent and may not be transferable between different operating / file systems."Adjudge
@ben75, I don't want to take this thread off-topic, but note that I was trying neither to use the URI on a different operating system nor on another file system. I was simply trying to create a URI to a file using Java's URI and Android's Uri and wind up with the same identifier. Surely everyone agrees that would be helpful. As a developer I'm trying to program application logic and don't want to waste a day creating URI<->Uri conversion methods.Nosedive
K
3

Do not use Run As - Android JUnit Test as it is for running Android Test Project only.

When use Run As - JUnit Test, the ClassNotFoundException is due to inconsistence between ADT and Eclipse built-in JUnit Test Runner regards to the project output folder. ADT generates all .class files under bin/classes, whereas built-in JUnit Test Runner looking for .class files under target/classes. Your Android project in Eclipse never use target/classes so it remains empty, this is the reason why you get ClassNotFoundException exception.

AFAIK there is no way to alter Eclipse built-in JUnit Test Runner to use a different folder than the default target/classes. Check out Ricardo's answer to see how to add bin/classes to built-in JUnit Test Runner's classpath. Also note that you cannot alter your Android project's default output folder to something else than ../bin/classes either, as it will break ADT build process.

The dirty workaround (for solving ClassNotFoundException) is manual copy everything under bin\classes to target\classes, note that you need do this every time you change the source code.

This is not a problem when running mvn test from command line or via Eclipse, as Maven use target\classes and know how to fill it properly. note that by using this approach, you will not able to use the JUnit window with nice red/green error bar inside Eclipse.

Kuroshio answered 27/3, 2013 at 0:54 Comment(4)
So even if I forgo use of Eclipse for running the tests and run them manually, they still have to be able to compile in Eclipse and generate Dex files correctly. Yet as I mentioned in my comment, simply including JUnit in the dependencies (which is required for them to compile) results in an error that JUnit was double-included when I try to deploy the application? How do I get around that?Nosedive
In your POM, make sure you are using <scope>test</scope> for your JUnit dependency.Kuroshio
Yes, I am using test scope. Have you tried to add JUnit tests to an Android library using Maven, and then incorporate that library into a separate Android app project, with multiple of these projects using JUnit dependencies in test scope?Nosedive
It doesn't matter if both app project and library project use JUnit dependency at the same time, as an example, see morseflash sample project. Also note that the classical JUnit test is running independently and before the final apk is built, and the final apk will exclude all test classes and JUnit dependency.Kuroshio
I
3

A workaround is to click the "Run As..." button in the toolbar and then select "Run Configurations...". If you select the JUnit launcher you created and go to its "Classpath" tab you can add the bin/classes folder to the JUnit launcher classpath. This should now run.

Insured answered 16/6, 2013 at 19:20 Comment(1)
This is it! Works like a champ. Just to make it a bit more clear (I struggled a bit to understand what Ricardo said). Right click your test project -> Run as -> Run configurations -> On the Test tab, look below for "Using Android JUnit Test Launcher" -> Click "Select other" -> Select the appropriate one -> Ok -> Run! There you go!Semang
D
0

An android junit test should be located in a separate module as setup in the Android Maven Plugin samples projects (e.g. the morseflash example). This is due to the overloaded path setting and the need to build the apk and deploy it on the device/emulator to run the test. Android junit tests are NOT unit tests at all, but rather integration test (or in this case called instrumentation tests).

Donnelly answered 26/3, 2013 at 23:31 Comment(7)
But what if I don't want to run the test on the device/emulator? What if I want to run it locally?Nosedive
If it is a Android junit test you can not run it locally. Thats how these tests work. If you need to run locally .. use Robolectric.Donnelly
Then how do you interpret the android-maven-plugin documentation which says, "If you have JUnit tests that don't call any Android APIs (directly or transitively), put them in src/test/java so JUnit runs them locally and more quickly than if run on the device."Nosedive
Those are standard junit tests .. they can NOT access any Android classes.. and are NOT the same as the Android Junit tests from the Android SDK/ADT. I wrote that documentation on the site and the examples app that displays it (morseflash... )Donnelly
@ManfredMoser I'm tryng to run plain old JUnit tests for plain java classes (that are in an Android project) by setting <testSourceDirectory> and then having tests extend TestCase. They get compiled but surefire doesn't execute them. I've tried copying moreseflash.... Any ideas?Tadio
I assume it's something to do with my library being packaged as a apklib and having the android-maven-plugin involved in packaging the overall library - even if some classes are plain java? :-(Tadio
The plain java classes should be in a separate jar and project and there you can run plain old unit tests.Donnelly

© 2022 - 2024 — McMap. All rights reserved.