Craig Walls has been professionally developing software for over 14 years (and longer than that for the pure geekiness of it). He is the author of Spring in Action (now in its second edition) and XDoclet in Action, both published by Manning and is currently writing about OSGi and Spring-DM. Craig has posted 9 posts at DZone. View Full User Profile

Testing OSGi...Spring Style

08.03.2009
| 6315 views |
  • submit to reddit

Last week I showed you how to test OSGi bundles. We used Pax Exam to fire up an OSGi runtime of our choosing, install and start a selection of bundles, and to make assertions against the BundleContext and services registered in the OSGi service registry. This week, we're going to repeat that exercise, this time using Spring-DM's testing support.

Spring-DM's testing support works very much like Pax Exam. When a test is run, it will start an OSGi runtime, install a selection of bundles, create an on-the-fly bundle containing the test class, and then install the test bundle into the OSGi runtime so that it can perform its assertions from the inside.

At the center of Spring-DM's OSGi testing framework is AbstractConfigurableBundleCreatorTests, a JUnit 3 base class that all bundle tests will extend. It provides the basic functionality needed to access the OSGi BundleContext, consume services, and make assertions about your bundles.

I know what you're thinking...JUnit 3? Really? Yes, Spring-DM's testing support is currently based on JUnit 3. But there's plans to move it to JUnit 4 in Spring-DM 2.0.0. If you're interested in following the progress, then have a look at OSGI-410. In the meantime, we'll have to work with what we've got.

So let's get started writing a Spring-DM bundle test. We'll start with the basics and build it up as we go.

public class PigLatinTranslatorBundleTest 
      extends AbstractConfigurableBundleCreatorTests {

   @Override
   protected String[] getTestBundlesNames() {
      return new String[] {
          "com.habuma.translator, interface, 1.0.0",
          "com.habuma.translator, pig-latin, 1.0.0"
        };
   }   

   // test methods go here
}

Aside from extending AbstractConfigurableBundleCreatorTests, the first thing we're going to do is override the getTestBundlesNames. AbstractConfigurableBundleCreatorTests doesn't require us to override this method (or any method, for that matter), but if we don't override this method, the OSGi runtime will be started without much to test. So, I've overridden this method to identify the bundles we want installed in our test scenario.

Much like how we started our Pax Exam test last week, here we're specifying the bundles as Maven dependencies. But instead of using a fluent API, as with Pax Exam, we return an array of String where each member is a comma-separated list of group ID, artifact ID, and version number (and optionally type). Also unlike the Pax Exam version, we didn't have to explicitly ask for Spring-DM bundles to be installed. That's because Spring-DM is assumed and will be installed by default.

Now we can write the first test method. Just like we did with Pax Exam, we'll start by testing that the OSGi runtime starts okay by asserting that we have a BundleContext:

public void testOsgiPlatformStarts() {
   assertNotNull(bundleContext);
}

The bundleContext variable is a given for test classes that extend AbstractConfigurableBundleCreatorTests. If the OSGi runtime fails to start for any reason, bundleContext will be null. But if there aren't any problems, we'll have a reference to the BundleContext through which we can make some assertions.

Next up, we want to be sure that the Pig Latin bundle registers a Translator service in the OSGi service registry and that the service has its "translator.language" property set to indicate that it is a Pig Latin translator. There are a couple of ways to accomplish this using AbstractConfigurableBundleCreatorTests, but let's first try by mimicking the style of test we wrote last time with Pax Exam:

public void testServiceReferenceExists() {
   ServiceReference serviceReference = 
      bundleContext.getServiceReference(Translator.class.getName());
   assertNotNull(serviceReference);
   assertEquals("Pig Latin", 
         serviceReference.getProperty("translator.language"));
}

This method should be familiar, as it is almost identical to the serviceReferenceShouldExist() method from the Pax Exam example (the name has been changed to accommodate JUnit 3's conventions). Here, it goes directly to the BundleContext to get a ServiceReference for our service. From that ServiceReference, it makes the assertions necessary to satisfy the requirements.

The testServiceReferenceExists() method works using a more conventional programmatic approach to working with OSGi services. But this is a Spring-DM test--we can leverage Spring-DM to declaratively get a reference to the Translator service. To do that, we'll need to create a Spring context configuration that references the service:

<?xml version="1.0" encoding="UTF-8"?> 
<beans:beans
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/osgi"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/osgi
http://www.springframework.org/schema/osgi/spring-osgi.xsd">

<reference id="translator"
    interface="com.habuma.translator.Translator"
    filter="(translator.language=Pig Latin)" />

</beans:beans>

The <reference> element from Spring-DM's configuration namespace creates a bean in the Spring application context that is a proxy to the OSGi service. In this case, the service we're looking for is one that implements com.habuma.translator.Translator and whose "translator.language" property is set to "Pig Latin".

I talk more about Spring-DM's declarative service model in Modular Java and in my DZone Spring-DM RefCard

Next, we'll need to override AbstractConfigurableBundleCreatorTests's getConfigLocations() method to tell our test about the Spring configuration file:

@Override
protected String[] getConfigLocations() {
   return new String[] { "bundle-test-context.xml" };
}

The getConfigLocations() method identifies one or more Spring configuration files so that they can be included in the on-the-fly bundle. When a Spring-DM bundle test starts, it will load a Spring application context using the configuration files. In this case, that Spring context will include a reference to a Translator service.

Now we have a Spring context with a reference to a Translator service and our test class knows about the Spring context. The only setup work left to do is to create a property in the test class to hold the reference to the service:

public class PigLatinTranslatorBundleTest 
      extends AbstractConfigurableBundleCreatorTests {

   private Translator translator;
   public void setTranslator(Translator translator) {
      this.translator = translator;
   }

   // ...
}

When the Spring-DM test bundle starts and when its Spring application context is created, the translator property will automatically get set through the setTranslator method, wiring it with the service reference from bundle-test-context.xml. With the Translator service reference in hand, we're ready to test the service:

public void testTranslatorService() {     
   assertNotNull(translator);
   assertEquals("id-DAY is-thAY ork-wAY", 
         translator.translate("Did this work"));
}

The test method starts by asserting that we actually have a translator to work with. Then, we toss it some text to be sure that the translator can properly translate it into Pig Latin.

And that's it. We now have a Spring-DM bundle test that's roughly equivalent to what we wrote last week using Pax Exam. The finished test class, in its entirety, is as follows:

package com.habuma.translator.test;
import org.osgi.framework.ServiceReference;
import org.springframework.osgi.test.AbstractConfigurableBundleCreatorTests;
import com.habuma.translator.Translator;

public class PigLatinTranslatorBundleTest 
      extends AbstractConfigurableBundleCreatorTests {
   private Translator translator;

   public void setTranslator(Translator translator) {
      this.translator = translator;
   }
   
   @Override
   protected String[] getTestBundlesNames() {
      return new String[] {
          "com.habuma.translator, interface, 1.0.0",
          "com.habuma.translator, pig-latin, 1.0.0"
        };
   }   
    
   @Override
   protected String[] getConfigLocations() {
      return new String[] { "bundle-test-context.xml" };
   }
   
   public void testOsgiPlatformStarts() {
      assertNotNull(bundleContext);
   }
   
   public void testServiceReferenceExists() {
      ServiceReference serviceReference = 
         bundleContext.getServiceReference(Translator.class.getName());
      assertNotNull(serviceReference);
      assertEquals("Pig Latin", 
            serviceReference.getProperty("translator.language"));
   }
   
   public void testTranslatorService() {     
      assertNotNull(translator);
      assertEquals("id-DAY is-thAY ork-wAY", 
            translator.translate("Did this work"));
   }
}

Testing options

With Pax Exam, we were able to resolve test bundles using any means available to Pax Runner, including Maven, filesystem, and HTTP URLs. Unfortunately, Spring-DM's testing support is limited to resolving bundles as Maven artifacts. (Although you can opt to override getTestBundles() instead of getTestBundlesNames() to resolve bundles yourself using whatever means you'd like.)

But, we can tweak the OSGi runtime. By default, Spring-DM testing will use Equinox, but you can switch to Felix by overriding the getPlatformName() method:

protected String getPlatformName() {
   return Platforms.FELIX;
}

Or, if you're more of a Knopflerfish kind of person, then:

protected String getPlatformName() {
   return Platforms.KNOPFLERFISH;
}

Unlike Pax Exam, however, Spring-DM testing currently only supports a single version of these frameworks (specifically, Spring-DM 1.2.0 uses Equinox 3.2.2, Felix 1.4.1, or Knopflerfish 2.2.0). And, also unlike Pax Exam, you must choose a single runtime...you can't ask Spring-DM to run your tests on more than one OSGi runtime. (Although you could write separate tests that use different runtimes.)

Should I use Pax Exam or Spring-DM testing?

I'm not going to tell you which testing framework to use--I'll leave it up to you to pick which one suits you best. But I will share my opinions on the subject...maybe it will help you decide.

I like the way that Spring-DM's testing automatically wires in service references for my test to consume so that I don't have to look them up myself in my tests. But, given that Spring-DM's testing support is currently based on JUnit 3, I'm a little turned off from it. Furthermore, given the limitations in bundle resolution and platform selection, I find Spring-DM testing to be somewhat inferior to Pax Exam.

That said, I believe both sides could learn a bit from each other. Spring-DM's testing could obviously be bumped up to be based on JUnit 4 and to be more flexible with regard to bundle resolution and platform selection. Meanwhile, Pax Exam could learn a little from Spring-DM testing on how to wire test properties with OSGi services.

In any event, whether you use Pax Exam or Spring-DM testing, we can all agree that testing is a worthwhile practice. And regardless of which one you use, there's no reason to not extend that good practice into OSGi development.

That's it for this week. I'm still scheming on what I want to write about for next time--I won't tell you everything that I've got in mind, but I can say that I'm thinking of showing you a way to test OSGi-based applications in a way that is difficult (or perhaps even impossible) without OSGi's dynamic runtime. Keep an eye on this blog to get the details.

From http://www.jroller.com/habuma

Published at DZone with permission of its author, Craig Walls.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags: