Unit test Java class that loads native library

I'm running unit tests in Android Studio. I have a Java class that loads a native library with the following code

 static
    {
       System.loadLibrary("mylibrary");
    }

But when I test this class inside my src/test directory I get

java.lang.UnsatisfiedLinkError: no mylibrary in java.library.path
    at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1864)
    at java.lang.Runtime.loadLibrary0(Runtime.java:870)
    at java.lang.System.loadLibrary(System.java:1122)

How can I make it find the path of native .so libraries which is located at src/main/libs in order to unit test without errors?

Note: inside src/main/libs directory I have 3 more subdirectories: armeabi, mips and x86. Each one of those contains the proper .so file. I'm using the Non experimental version for building NDK libs.

I don't wanna use other 3rd party testing libraries as all my other "pure" java classes can be unit tested fine. But if that's not possible then I'm open to alternatives.

Here is my test code which throws the error

   @Test
    public void testNativeClass() throws Exception
    {
        MyNativeJavaClass test = new MyNativeJavaClass("lalalal")
        List<String> results = test.getResultsFromNativeMethodAndPutThemInArrayList();
        assertEquals("There should be only three result", 3, results.size());
    }

Solution 1:

The only solution I found that works without hacks is to use JUnit through instrumentation testing (androidTest directory). My class can now be tested fine but with help of the android device or emulator.

Solution 2:

If the library is required for your test, use an AndroidTest (under src/androidTest/...) rather than a junit test. This will allow you to load and use the native library like you do elsewhere in your code.

If the library is not required for your test, simply wrap the system load in a try/catch. This will allow the JNI class to still work in junit tests (under src/test/...) and it is a safe workaround, given that it is unlikely to mask the error (something else will certainly fail, if the native lib is actually needed). From there, you can use something like mockito to stub out any method calls that still hit the JNI library.

For example in Kotlin:

    companion object {
        init {
            try {
                System.loadLibrary("mylibrary")
            } catch (e: UnsatisfiedLinkError) {
                // log the error or track it in analytics
            }
        }
    }

Solution 3:

I am not sure whether this solves your problem or not but so far nobody has mentioned about strategy pattern for dealing with classes preloading library during their creation.

Let's see the example:

We want to implement Fibonacci solver class. Assuming that we provided implementation in the native code and managed to generate the native library, we can implement the following:

public interface Fibonacci {
     long calculate(int steps);
}

Firstly, we provide our native implementation:

public final class FibonacciNative implements Fibonacci {
    static {
      System.loadLibrary("myfibonacci");
    }

    public native long calculate(int steps);
}

Secondly, we provide Java implementation for Fibonacci solver:

public final class FibonacciJava implements Fibonacci {

   @Override
   public long calculate(int steps) {
       if(steps > 1) {
           return calculate(steps-2) + calculate(steps-1);
       }
       return steps;
   }
}

Thirdly, we wrap the solvers with parental class choosing its own implementation during its instantiation:

public class FibonnaciSolver implements Fibonacci {

   private static final Fibonacci STRATEGY;

   static {
      Fibonacci implementation;
      try {
         implementation = new FibonnaciNative();
      } catch(Throwable e) {
         implementation = new FibonnaciJava();
      }

      STRATEGY = implementation;
   }

   @Override
   public long calculate(int steps) {
       return STRATEGY.calculate(steps);
   }

}

Thus, the problem with finding path to the library using strategy. This case, however, does not resolve the problem if the native library is really necessary to be included during the test. It does not neither solve the problem if the native library is a third-party library.

Basically, this gets around the native library load problem by mocking out the native code for java code.

Hope this helps somehow:)