10

The Application object for my Android app loads a JNI library, and Robolectric doesn't seem to like that. When I go to run my tests Robolectric craps out and I get this stack trace:

java.lang.UnsatisfiedLinkError: no cperryinc-jni in java.library.path at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1758) at java.lang.Runtime.loadLibrary0(Runtime.java:823) at java.lang.System.loadLibrary(System.java:1045) at com.cperryinc.application.MoolaApplication.(MoolaApplication.java:24) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:169) at com.xtremelabs.robolectric.internal.ClassNameResolver.safeClassForName(ClassNameResolver.java:36) at com.xtremelabs.robolectric.internal.ClassNameResolver.resolve(ClassNameResolver.java:15) at com.xtremelabs.robolectric.ApplicationResolver.newApplicationInstance(ApplicationResolver.java:71) at com.xtremelabs.robolectric.ApplicationResolver.resolveApplication(ApplicationResolver.java:28) at com.xtremelabs.robolectric.RobolectricTestRunner.createApplication(RobolectricTestRunner.java:483) at com.xtremelabs.robolectric.RobolectricTestRunner.setupApplicationState(RobolectricTestRunner.java:360) at com.xtremelabs.robolectric.RobolectricTestRunner.internalBeforeTest(RobolectricTestRunner.java:299) at com.xtremelabs.robolectric.RobolectricTestRunner.methodBlock(RobolectricTestRunner.java:277) at org.junit.runners.BlockJUnit4ClassRunner.runNotIgnored(BlockJUnit4ClassRunner.java:79) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:71) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184) at org.junit.runners.ParentRunner.run(ParentRunner.java:236) at org.junit.runner.JUnitCore.run(JUnitCore.java:157) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:76) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:182) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:62) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)

I'm not exactly sure what I can do about this. Any ideas on a workaround?

Christopher Perry
  • 38,891
  • 43
  • 145
  • 187
  • 1
    Using native methods in Android Framework-derived classes (Application, Activity, Fragment, etc.) is not a good idea. Even beyond Robolectric, you loose control on when and how the native library is loaded. It is usually worthwhile to wrap all native methods in separate classes that don't inherit from Android framework. This will let you test the native methods separately, and mock the native methods when testing the application. – Alex Cohn Sep 27 '17 at 09:41

3 Answers3

5

Solution works for Robolectric 1.2, not 2.+

Thanks to Jan Berkel for answering this here: https://groups.google.com/d/msg/robolectric/beW9XjT8E1A/pJQrRaybN30J

class MyJniClass {
 static {
        try {
            System.loadLibrary("libname");
        } catch (UnsatisfiedLinkError e) {
            // only ignore exception in non-android env
            if ("Dalvik".equals(System.getProperty("java.vm.name"))) throw e;
        }
    }
}

then in the testrunner:

public class MyTestRunner  extends RobolectricTestRunner {
   public MyTestRunner(Class testClass) throws InitializationError {
         // remove native calls + replace with shadows
        addClassOrPackageToInstrument("com.example.jni.MyJniClass");
   }

   protected void bindShadowClasses() {
         // bind shadow JNI classes
   }
}
Community
  • 1
  • 1
Christopher Perry
  • 38,891
  • 43
  • 145
  • 187
2

As @Jared pointed out, the solution @Christopher gave doesn't work for Robolectric 2 or 3.

The solution I ended up using was to add the environmental variable:

ROBOLECTRIC=TRUE

to the build configuration for my tests. (Run -> Edit Configurations, Environmental Variables).

Then you check for that environmental variable before loading problematic libraries. For example:

class MyClass {
    if(System.getenv("ROBOLECTRIC") == null) {
        System.loadLibrary("libname");
    }
}

Obviously you won't be able to test any code that relies on that library, but at least some testing will be possibel!

dbdkmezz
  • 98
  • 1
  • 6
1

One option for apps with heavyweight Application classes that don't work well with Robolectric is to create an empty Application object and use this for your Robolectric tests:

Something like this:

public void EmptyApp extends Application { 
}

Then your test setup can look like this:

@RunWith(RobolectricTestRunner.class)
@Config(application = EmptyApplication.class, manifest = "src/main/AndroidManifest.xml", sdk = 23)   

Since you have referenced the manifest, all of the resources will still be available in Context#getString(int id) and so on.

David Rawson
  • 20,912
  • 7
  • 88
  • 124