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());
}
You can use JUnit for both unit and integration testing and it also supports Java 8 features. Btw, if you are a completely new in the unit testing world, particularly in Java unit testing then this JUnit and Mockito crash course is a good starting point.
Chutzpah is an open source JavaScript test runner which helps you integrate JavaScript unit testing into your website. It enables you to run JavaScript unit tests from the command line and from inside of Visual Studio. It also supports running in the TeamCity continuous integration server.
A native library is a library that contains "native" code. That is, code that has been compiled for a specific hardware architecture or operating system such as x86 or windows. Including such native library in your project may break the platform-independence of you application.
Unit testing refers to the testing of individual components in the source code, such as classes and their provided methods. The writing of tests reveals whether each class and method observes or deviates from the guideline of each method and class having a single, clear responsibility.
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.
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
}
}
}
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:)
There is a way to configure library path of Gradle-run VM for local unit tests, and I'm going to describe it below, but spoiler: in my expericence, @ThanosFisherman is right: local unit tests for stuff that uses the Android NDK seem to be a fools errand right now.
So, for anyone else looking for a way to load shared (i.e. .so
) libraries into unit tests with gradle, here's the somewhat lengthy abstract:
The goal is to set the shared library lookup path for the JVM running the unit tests.
Althoug many people suggest putting the lib path into java.library.path
, I found that it doesn't work, at least not on my linux machine. (also, same results in this CodeRanch thread)
What does work though is setting the LD_LIBRARY_PATH
os environment variable (or PATH
is the closest synonym in Windows)
Using Gradle:
// module-level build.gradle
apply plugin: 'com.android.library' // or application
android {
...
testOptions {
unitTests {
all {
// This is where we have access to the properties of gradle's Test class,
// look it up if you want to customize more test parameters
// next we take our cmake output dir for whatever architecture
// you can also put some 3rd party libs here, or override
// the implicitly linked stuff (libc, libm and others)
def libpath = '' + projectDir + '/build/intermediates/cmake/debug/obj/x86_64/'
+':/home/developer/my-project/some-sdk/lib'
environment 'LD_LIBRARY_PATH', libpath
}
}
}
}
With that, you can run, e.g. ./gradlew :mymodule:testDebugUnitTest
and the native libs will be looked for in the paths that you specified.
Using Android Studio JUnit plugin For the Android Studio's JUnit plugin, you can specify the VM options and the environment variables in the test configuration's settings, so just run a JUnit test (right-clicking on a test method or whatever) and then edit the Run Configuration:
Although it sounds like "mission accomplished", I found that when using libc.so, libm.so
and others from my os /usr/lib
gives me version errors (probably because my own library is compiled by cmake with the android ndk toolkit against it's own platform libs). And using the platform libs from the ndk packages brought down the JVM wih a SIGSEGV
error (due to incompatibility of the ndk platform libs with the host os environment)
Update As @AlexCohn incisively pointed out in the comments, one has to build against the host environment libs for this to work; even though your machine most likely is x86_64, the x86_64 binaries built against NDK environment will not do.
There may be something I overlooked, obviously, and I'll appreciate any feedback, but for now I'm dropping the whole idea in favor of instrumented tests.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With