I've generated a native library using ndk-build
which I'm able to load and use with in my Android application. However, I want to write some tests against this part of my app.
When calling the native function in my tests, I recieve this exception message:
java.lang.UnsatisfiedLinkError: no process in java.library.path
...where process
is my native library to import, named libprocess.so
.
I'm using Roboelectric for my tests, and running this particular one with the RobolectricTestRunner
, if it makes a difference.
How can I get my test project to 'see' the native library?
Edit: I'm loading the library in my app like so:
static { System.loadLibrary("process"); } public static native int[] process(double[][] data);
calling Process.process(array)
works fine in the app (the library is loaded), but fails when run from the tests with the exception given above.
Edit 2: If I set -Djava.library.path="<the directory of libprocess.so>"
as a VM argument, then:
System.out.println(System.getProperty("java.library.path"));
does show the path I set, but I still get the same exception. I'm setting the directory as:
<project-name>/libs/x86
...but as an absolute path.
Or: you could try putting your library into /res in the project and use System. load() instead of System. loadLibrary() to load it.
Instrumentation Test Support There is experimental support for Android instrumentation tests, which requires some additional configuration & dependencies. Furthermore, because JUnit 5 is built on Java 8 from the ground up, its instrumentation tests will only run on devices running Android 8.0 (API 26) or newer.
Mockito. Mockito is an open-source and one of the preferred Java unit testing frameworks. This well-known Java-based mocking framework is primarily used for Java app unit testing.
For anyone still looking, blork had the right idea - you need to compile your native libraries for your 'native' platform (Windows, Linux, Mac). The Android NDK builds libraries for the Android platform (.so files - might also work on Linux), and this is why there are no issues running in Activity Test Cases (because it loads up an Android instance).
To get the low-level, hella fast JUnit tests running, you need to support your JVM. On Windows, this might be building DLLs, on Apple, it's building dylibs (assuming shared libraries).
I've just completed a sample in my android-ndk-swig-example repo (https://github.com/sureshjoshi/android-ndk-swig-example/issues/9).
Basically, in my CMakeLists, I added an Apple caveat:
# Need to create the .dylib and .jnilib files in order to run JUnit tests if (APPLE) # Ensure jni.h is found find_package(JNI REQUIRED) include_directories(${JAVA_INCLUDE_PATH})
And then I make sure Gradle runs for unit tests, but using the Mac build system (not NDK).
def osxDir = projectDir.absolutePath + '/.externalNativeBuild/cmake/debug/osx/' task createBuildDir() { def folder = new File(osxDir) if (!folder.exists()) { folder.mkdirs() } } task runCMake(type: Exec) { dependsOn createBuildDir workingDir osxDir // Jump to future build directory commandLine '/usr/local/bin/cmake' // Path from HomeBrew installation args '../../../../' // Relative path for out-of-source builds } task runMake(type: Exec) { dependsOn runCMake workingDir osxDir commandLine 'make' } project.afterEvaluate { // Not sure how much of a hack this is - but it allows CMake/SWIG to run before Android Studio // complains about missing generated files // TODO: Probably need a release hook too? javaPreCompileDebug.dependsOn externalNativeBuildDebug if (org.gradle.internal.os.OperatingSystem.current().isMacOsX()) { javaPreCompileDebugAndroidTest.dependsOn runMake } }
CAVEAT TIME!!!
When you use this method, you're technically not testing the NDK-generated libs. You're testing the same code, but compiled using a different compiler (msvc, xcode, gcc, clang, whatever you use on the host).
What this means practically, is that most of the test results will be valid - except when you run into problems caused by each compiler's quirks, or STL implementations, etc... This isn't as bad as it was 10+ years ago, but you can't say with 100% certainty that the results of JUnit testing with host libs is identical to the Android libs. You can say that it's reasonably close, though.
Then again, unless you're running your native unit tests using the Android NDK for each supported architecture, you also can't say anything about certainty either... So take what you will from it.
An overkill approach (but really cool if automated) would be to write your native unit tests however you do them (Google Test, Catch, etc), then compile and run your native libs and unit tests with the Android NDK per each architecture. This provides your C/C++ coverage across your potential target architectures.
From here, you could use the aforementioned host libs with JUnit to rapidly unit test your JNI layer interacting with your native lib. In your CI system, you should still probably run these same unit tests - but as Android Instrumentation tests (or something else that runs an emulated Android environment).
As with everything, wherever you have an interface, you can create mocks - but at some point, you'll need system/functional/integration tests too.
Update:
More comprehensive explanation of above in a blog article (http://www.sureshjoshi.com/mobile/android-junit-native-libraries/)
I've switched to the default testing style (using ActivityUnitTestCase instead of RoboElectric) and it's now running fine. It's a shame I have to sacrifice the speed of test running, but running the tests on the emulator actually works. You could also create a shadow class for the JNI class, as detailed here:
Robolectric tanks on Application objects that load JNI libraries. Can I get a workaround?
Perhaps compiling the library for my machine would have worked, but I couldn't spend any more time on it.
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