Unit testing for OSGi projects in Gradle.
To use the library add the following to your build.gradle:
buildscript {
repositories {
// dependencies from MVN central (you can use others)
mavenCentral()
maven {
// adds the maven repo for this plugin
url "https://dl.bintray.com/osgi-unit/maven"
}
}
dependencies {
classpath 'com.asteroid.duck:osgi-unit-gradle:1.0.0'
}
}
apply plugin: 'com.asteroid.duck.osgi.junit'
This plugin will then let you write JUnit tests in the traditional way
in Gradle (e.g. as JUnit4 compliant .java
files in the src/test/java
folder).
However, at runtime these tests can execute inside an OSGi container.
To do this the osgi-unit
library first creates and initialises an OSGi container for your tests;
The classpath for this OSGi framework is specified using the testFramework
dependency configuration.
After the OSGi framework is started any JARs (including other Gradle projects) from the
testRuntime
dependency container are added to the framework as bundles and started. As an example, the following
dependencies
section in your build.gradle
will cause the tests to run in Apache Felix container (with JUnit4):
dependencies {
compile 'org.osgi:org.osgi.core:4.+'
testCompile 'junit:junit:4.+'
testFramework 'org.apache.felix:org.apache.felix.framework:5.4.0'
}
Note: Dependencies (such as those in
compile
) must be OSGi compliant bundles to work! (for now) Regular JAR files without the necessary meta-data will simply fail to load.
In addition to starting the OSGi framework; the plugin will package your test classes into a 'special' JAR file (with the necessary meta-data) so this too can be deployed into the OSGi container.
Note: The name of the JAR will be the same as your projects main JAR with
-test
appended before the.jar
extension. (e.g.my-project_1.2.3-test.jar
)
So now your tests are deployed inside a running OSGi framework, you will want to acquire and use services in them? Here you have some options dependent on what you want to achieve.
If you want to acquire instances of OSGi services through the micro services registry you can use the WithOSGiService
rule. This rule will acquire and release an instance of the service (using official OSGi mechanisms) during the test
setup, and tear down phase respectively. You declare the rule as a public field in your test, passing it the service
interface you seek; For example:
@Rule
public WithOSGiService<MySimpleService> service = new WithOSGiService<MySimpleService>(MySimpleService.class);
To use the service in your test method you would then call:
// get the current instance
MySimpleService serviceInstance = service.getServiceInstance();
// call it
serviceInstance.doStuff();
// assert etc.
Alternatively you can add the WithOSGi
rule to your classes:
@Rule
public WithOSGi osgi = new WithOSGi();
This object then provides direct access to the OSGi framework and the test Bundle
(and it's BundleContext
). As an
example:
// the OSGi framework object
Framework framework = osgi.getFramework();
// the "test" bundle
Bundle testBundle = osgi.getTestBundle();
// the BundleContext of the test bundle
BundleContext ctx = osgi.getTestBundleContext();
Sadly your test classes can't be in the same package as the classes being tested (yet). Since OSGi (BND) gets very confused about Import/Export packages and it fails. to resolve the test bundle at runtime. I am sure this is solvable though.
- Add a regular JAR wrapper feature that allows all classes in it to be exported in OSGi
- Add the ability to control start order of bundles through our plugin extension; and have mandatory started JARs?
- Make the
WithOSGiService
rule track the service in case it comes/goes during testing - Make the
WithOSGiService
rule support exotic filters and service names
The project is divided into 2 sub-projects:
core
- which contains Java code used by the runtime (and referenced by the plugin for constants etc.)plugin
- which contains the Groovy code for the Gradle plugin
- The plugin first replaces the classpath of the
test
task with thetestFramework
JARs (and their dependencies). Including the osgi-unitcore
JAR. - During the build we grab the
testRuntime
classpath (and dependent JARs) and write this out (so we can load these into OSGi later...) - A special "test" JAR (bundle) is created as part of the build, this contains your test classes
- Upon launching the
test
task; we pass in lots of extra properties to let our JAR know where your test classes are what thetestRuntime
path was etc. - At launch we swap the
java.system.class.loader
for our own "freaky" classloader. This is the real trick... this classloader knows which classes to load from the "boot" classpath (i.e.testFramework
and the JVM classpath); and which to load from OSGi. - When the JUnit test executor tries to load your test class (or any other class outside the
"boot" classpath) - to do this the freaky class loader will :
- Start the OSGi framework
- Install the JARs (bundles) - which we squirreled away in step 2
- Start all the bundles
- Store a singleton reference to the OSGi framework (for later, when your tests run)
- Locate the "test" bundle - the JAR we created at step 3
- Ask the "test" bundle to load the class (and any classes it needs)
- Now a class is handed back to JUnit that came from OSGi
- JUnit creates instances of your class and runs them (as normal)
- If your JUnit uses our special JUnit rule classes, they can access the OSGi framework singleton from step 6 (iv)
- Your tests can use OSGi stuff like it was running for "normally"
The project source is maintained on github.com.
The project build is performed by Travis-CI, where the current status is:
The binaries are available from bintray.com