OSGi based Integration testing with TestNG and Apache Felix

Published: 10:05 PM GMT+12, Tuesday, 13 May 2008 under: technology
osgi  testng  java  testing  apache 

Over the last month or so I've been getting my feet wet with a new job, new team mates, and new technology - with the majority of my time getting my head around OSGi and our custom OSGi orientated build tool. Now that I've settled in and starting to see my code take shape it's starting to irk me that I have ZERO tests in any of the code I've written - everything has been a developed under a cycle of code, build, update, test, repeat whilst single stepping through the code in the debugger.

Initially I was 'ok' with the lack of tests as I was still experimenting inside the debugger, interactively hacking my way through foreign code. However now I need to move on from the hacking and get something a little more respectable along with some reproducible tests.

However, now I have a series of OSGi bundles which are exporting services, listening and interacting with other bundles, and doing all manner of things which crossed the boundary of a single bundle - this means I'm needing integration tests more than unit tests.

After thinking up various interesting, but overkill ways of OSGi-enabling TestNG to provide some nice integration testing I settled on the simplest approach (which also looks to be the best, and most flexible so far): embed felix - we already had a custom Felix launcher so this was very much similar (and so simple that it's almost not worth blogging about).

The basic test class simply starts and stops an embedded felix instance:

public class FelixTest {
  private Felix m_felix;

  @BeforeTest
  public void setupFelix() throws IOException, BundleException {
    Map configMap = new StringMap(false);
    configMap.put(Constants.FRAMEWORK_SYSTEMPACKAGES,
      "org.osgi.framework; version=1.3.0," +
      "org.osgi.service.packageadmin; version=1.2.0," +
      "org.osgi.service.startlevel; version=1.0.0," +
      "org.osgi.service.url; version=1.0.0");

    configMap.put(BundleCache.CACHE_PROFILE_PROP, "test");
    File file = File.createTempFile("osgi", "testng");
    file.delete();
    file.mkdir();
    file.deleteOnExit();

    configMap.put(BundleCache.CACHE_PROFILE_DIR_PROP, file.getPath());
    // Create an instance of the framework.
    m_felix = new Felix(configMap, new ArrayList());
    m_felix.start();
  }

  @AfterTest
  public void turnOffFelix() throws BundleException {
    m_felix.stop();
  }
}

This simply starts the Felix OSGi container, with a fresh/temp profile directory. Now comes the actual test:

  @Test
  public void testSomethingWithFelix() throws BundleException, InterruptedException {
    BundleContext bundleContext = m_felix.getBundleContext();

    bundleContext
      .installBundle("file:///Users/amrk/temp/osgi-test/test-rest/target/test-rest-1.0-SNAPSHOT.jar")
      .start();
    bundleContext
      .installBundle("file:///Users/amrk/temp/osgi-test/test-client/target/test-client-1.0-SNAPSHOT.jar")
      .start();

    Bundle[] bundles = bundleContext.getBundles();
    for (Bundle bundle : bundles) {
      assert bungle.getState() == Bundle.ACTIVE
        : "Bundle " + bundle.getSymbolicName() + " is not started.";
    }

    ServiceReference sr = bundleContext.getServiceReference(MyService.class.getName());
    if (sr != null) {
      Object myService = bundleContext.getService(sr);
      if (myService != null) {
        method("run").in(myService).invoke();
      }
    } else {
      assert false: "No Service Reference for MyService";
    }
  }

The test starts off by installing and starting two bundles from the file system then checks they all have the ACTIVE state (meaning they installed, resolved, and started running without problem). The test then looks up a service which should be exported from the test-rest bundle, then via reflection (using FEST-Reflect) executes the service. (reflection is used here as 'myService' service is an instance of MyService from the OSGi bundles class loader and not from the tests class loader.)

Now I have a good base for writing OSGi based integration tests that easily be extended with database creations/migrations and anything else that may come to mind.

Comments (4)

thx, this gave me some new ideas !

left by Tomek . Wednesday, 19 November 2008 8:35 PM

At the moment we have limited, but some unit tests inside each bundle which test various elements that don't require other bundles to operate. However our current build setup makes this a little difficult to report on, hopefully when we move to maven things will be better in this regard. As for knowing that they all work - thats were more of the integration test comes in, i.e. we have a bundle that provides a hibernate session, and bundles that contribute entities and rest services; the first layer of the integration tests deploy of the bundles, and wait for "a SessionFactory which contains class x, and a restful resource at uri xxx", if they timeout then we fail everything, but if we get them - then we go on to do integration testing of the restful API as now we expect that to work.

left by Mark Derricutt . Monday, 10 November 2008 1:34 PM

Good post, gave me heaps of ideas. I recently had a lot more trouble with embedding Equinox than you seem to of had with felix, but since I am going to try get a few frameworks for testing up and running you post will soon come in handy. What I would like to know is basically what kinds of other testing procedures do you have on bundles. How do you determine that the overall program will work, and that each individual bundle does what it is supposed to? Thanks Graham

left by Graham . Monday, 10 November 2008 1:15 PM

You might want to have a look at Pax Drone (http://wiki.ops4j.org/confluence/x/KABo) which uses a similar approach, sure it currently just supports Junit currently, but enables you to use all Felix Versions as well as Equinox and Knopflerfish in your tests. Cheers, Toni

left by Toni Menzel . Tuesday, 30 September 2008 9:24 PM
Add Comment