Skip to content
CMO & CTO
CMO & CTO

Closing the Bridge Between Marketing and Technology, By Luis Fernandez

  • Digital Experience
    • Experience Strategy
    • Experience-Driven Commerce
    • Multi-Channel Experience
    • Personalization & Targeting
    • SEO & Performance
    • User Journey & Behavior
  • Marketing Technologies
    • Analytics & Measurement
    • Content Management Systems
    • Customer Data Platforms
    • Digital Asset Management
    • Marketing Automation
    • MarTech Stack & Strategy
    • Technology Buying & ROI
  • Software Engineering
    • Software Engineering
    • Software Architecture
    • General Software
    • Development Practices
    • Productivity & Workflow
    • Code
    • Engineering Management
    • Business of Software
    • Code
    • Digital Transformation
    • Systems Thinking
    • Technical Implementation
  • About
CMO & CTO

Closing the Bridge Between Marketing and Technology, By Luis Fernandez

Testing OSGi Bundles: Isolation that Helps

Posted on January 25, 2011 By Luis Fernandez

Why does testing an OSGi bundle feel trickier than testing a plain JAR?

OSGi bundle testing has a reputation for being hard because the thing you ship is not a jar that sits alone. It joins a living world with a service registry, dynamic lifecycle, and class loaders that do not let you cheat. That world gives us clean boundaries and versioned contracts. It also gives us ways to shoot our foot if we do not test the right way. The upside is real. If you get the tests right, you can lean on isolation to keep your bundle honest and you can swap frameworks like Equinox or Felix without drama. Let me share what works for me right now, with tools we are all using today like JUnit, Mockito, Pax Exam, bnd, and Maven.

Short answer first. Isolation helps.

What should we test in an OSGi world?

Think in contracts. Your bundle promises to provide services with a name, a version, and a set of service properties. It promises to consume packages with a range like [1.2,2). Start by writing tests that lock those promises. I call them contract tests. A contract test for a provider spins a tiny container, installs the bundle, finds the service by interface and properties, and runs behavior checks. A contract test for a consumer feeds it a fake provider and confirms it accepts the right versions and refuses the wrong ones. This catches broken Import-Package ranges and surprises with weaving at runtime. You can do this on Felix or Equinox and compare outcomes, which is very handy when a quirk shows up on only one.

Black box first.

Pax Exam to boot a tiny OSGi for real

For integration tests in OSGi I use Pax Exam. It starts a framework in the same JVM as the test and lets you drop bundles in with a fluent config. It is quick enough for the build and it gives you the real registry. Here is a simple JUnit 4 test that proves a service gets registered with the right property and does something useful. This runs on Felix by default, but you can switch to Equinox with one line.

import static org.junit.Assert.*;
import static org.ops4j.pax.exam.CoreOptions.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.junit.JUnit4TestRunner;
import org.ops4j.pax.exam.Option;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;

@RunWith(JUnit4TestRunner.class)
public class GreetingServiceIT {

  @org.ops4j.pax.exam.Configuration
  public Option[] config() {
    return options(
      junitBundles(),
      felix(), // swap to equinox() to compare behavior
      mavenBundle("com.myco", "greetings-bundle", "1.0.0"),
      mavenBundle("org.mockito", "mockito-core", "1.8.5")
    );
  }

  @org.ops4j.pax.exam.Inject
  private BundleContext context;

  @Test
  public void service_is_registered_with_language_property() {
    ServiceReference ref = context.getServiceReference(
      "com.myco.greetings.api.GreetingService");
    assertNotNull("Service not found", ref);
    assertEquals("en", ref.getProperty("lang"));
    Object svc = context.getService(ref);
    String msg = ((com.myco.greetings.api.GreetingService) svc).greet("OSGi");
    assertEquals("Hello OSGi", msg);
  }
}

Fast tests keep you honest.

Unit tests without a container

Not every test needs the framework. Most logic lives behind an interface. Write pure unit tests for those classes. When you need BundleContext, pass it in and stub only the calls you use. Mockito works fine. This keeps feedback fast and lets you run hundreds of checks in a few seconds. Save Pax Exam for wiring and registration tests. Your build will thank you and your laptop fan too.

public class ActivatorTest {

  @Test
  public void registers_service_on_start() throws Exception {
    BundleContext ctx = mock(BundleContext.class);
    when(ctx.registerService(
      eq(GreetingService.class.getName()),
      any(GreetingService.class),
      any(Dictionary.class))
    ).thenReturn(mock(ServiceRegistration.class));

    GreetingActivator activator = new GreetingActivator();
    activator.start(ctx);

    verify(ctx).registerService(
      eq(GreetingService.class.getName()),
      any(GreetingService.class),
      argThat(dict -> "en".equals(dict.get("lang"))));
  }
}

No framework. No surprises.

Wire by packages, not by bundles

Use Import Package and Export Package with ranges. Avoid Require Bundle. Package imports make version checks clear and keep you portable across containers. With bnd or the Maven Bundle Plugin you can control it in a few lines. Also pick a simple semantic version rule and test it. Contract tests that fail on wrong ranges keep your house in order.

<plugin>
  <groupId>org.apache.felix</groupId>
  <artifactId>maven-bundle-plugin</artifactId>
  <version>2.3.7</version>
  <extensions>true</extensions>
  <configuration>
    <instructions>
      <Export-Package>
        com.myco.greetings.api;version=${project.version}
      </Export-Package>
      <Import-Package>
        com.myco.greetings.api;version="[1.0,2)",
        *
      </Import-Package>
      <Private-Package>com.myco.greetings.internal</Private-Package>
    </instructions>
  </configuration>
</plugin>

Exports tell stories.

Keep the container small

When you boot a container for tests, load only what you need. A common mistake is to pull a whole stack with a giant feature file. Start with the framework, your bundle, and direct dependencies. If you use DS or Blueprint, add just those runtime bundles. You get faster feedback and fewer false positives. Pax Exam options let you pick exact bundles by group and artifact which is perfect for repeatable builds on Jenkins or Hudson during this lively rename debate week.

Small beats big.

CI tips for OSGi tests

Make your continuous integration runner install the same versions you test locally. Cache the Maven repo on the build agent. Run unit tests first, then the Pax Exam suite. If you can, run the same suite twice, once on Felix and once on Equinox. That catches container quirks early. Add a daily job that runs with an older Java and a newer one. We still live on Java 6 in many places, and surprises creep in. Keep logs from the framework console in the build artifacts. When something fails on the server and not locally, those logs save time.

Repeatable beats clever.

Avoid state leaks and singletons

OSGi shakes out global state. Let it. Avoid static singletons in services. They pollute tests across runs, especially when the framework restarts in the same JVM. Prefer components that take their collaborators in constructors. For Declarative Services, test your logic without DS first, then add a thin test that checks activation and registration. If you use Felix SCR, you can simulate activation like this and keep it simple.

public class EmailNotifierComponent {

  private Mailer mailer;

  // DS will call this
  public void activate(Map<String, Object> config) {
    String host = (String) config.get("smtp.host");
    this.mailer = new SmtpMailer(host);
  }

  void setMailer(Mailer m) { this.mailer = m; } // for unit tests

  public void notify(String to, String body) { mailer.send(to, body); }
}

@Test
public void notify_uses_injected_mailer_without_ds() {
  EmailNotifierComponent cmp = new EmailNotifierComponent();
  Mailer mock = mock(Mailer.class);
  cmp.setMailer(mock);
  cmp.notify("a@b", "hi");
  verify(mock).send("a@b", "hi");
}

State leaks kill tests.

Make the registry talk

When something is off, print the service graph. A tiny helper that lists registered services with properties saves time. Add it to failing tests so you see what the container sees. The Equinox console or Gogo shell can show this too, but a quick dump in the test output is gold on a CI server. Keep the helper in test sources.

static void dumpServices(BundleContext ctx) {
  try {
    ServiceReference[] refs = ctx.getAllServiceReferences(null, null);
    if (refs == null) return;
    for (ServiceReference ref : refs) {
      Object clazzes = ref.getProperty("objectClass");
      System.out.println("Service: " + Arrays.toString((String[]) clazzes));
      for (String key : ref.getPropertyKeys()) {
        System.out.println("  " + key + " = " + ref.getProperty(key));
      }
    }
  } catch (Exception e) {
    e.printStackTrace();
  }
}

Make the invisible visible.

The core recipe

  • Isolate logic with plain unit tests and keep OSGi out of the picture unless you need it
  • Prove contracts with small Pax Exam suites that check registration, properties, and version ranges
  • Wire by packages, not by bundles, and lock ranges in tests
  • Keep containers tiny for speed and clarity
  • Run on both Felix and Equinox when possible to catch container quirks

Small honest bundles pass small honest tests.

Development Practices Software Architecture Software Engineering

Post navigation

Previous post
Next post
  • Digital Experience (94)
    • Experience Strategy (19)
    • Experience-Driven Commerce (5)
    • Multi-Channel Experience (9)
    • Personalization & Targeting (21)
    • SEO & Performance (10)
  • Marketing Technologies (92)
    • Analytics & Measurement (14)
    • Content Management Systems (45)
    • Customer Data Platforms (4)
    • Digital Asset Management (8)
    • Marketing Automation (6)
    • MarTech Stack & Strategy (10)
    • Technology Buying & ROI (3)
  • Software Engineering (310)
    • Business of Software (20)
    • Code (30)
    • Development Practices (52)
    • Digital Transformation (21)
    • Engineering Management (25)
    • General Software (82)
    • Productivity & Workflow (30)
    • Software Architecture (85)
    • Technical Implementation (23)
  • 2025 (12)
  • 2024 (8)
  • 2023 (18)
  • 2022 (13)
  • 2021 (3)
  • 2020 (8)
  • 2019 (8)
  • 2018 (23)
  • 2017 (17)
  • 2016 (40)
  • 2015 (37)
  • 2014 (25)
  • 2013 (28)
  • 2012 (24)
  • 2011 (30)
  • 2010 (42)
  • 2009 (25)
  • 2008 (13)
  • 2007 (33)
  • 2006 (26)

Ab Testing Adobe Adobe Analytics Adobe Target AEM agile-methodologies Analytics architecture-patterns CDP CMS coding-practices content-marketing Content Supply Chain Conversion Optimization Core Web Vitals customer-education Customer Data Platform Customer Experience Customer Journey DAM Data Layer Data Unification documentation DXP Individualization java Martech metrics mobile-development Mobile First Multichannel Omnichannel Personalization product-strategy project-management Responsive Design Search Engine Optimization Segmentation seo spring Targeting Tracking user-experience User Journey web-development

©2025 CMO & CTO | WordPress Theme by SuperbThemes