Get ahead
VMware offers training and certification to turbo-charge your progress.
Learn moreI'm pleased to announce the 1.0.0.M1 release of SpringSource's OSGi Test Stubs. These stubs offer a way to unit test complex OSGi Framework interactions without needing a full OSGi container.
@Test
public void startAndStop() throws Exception {
BundleActivator bundleActivator = new DumpBundleActivator();
BundleContext context = createMock(BundleContext.class);
Filter filter = createMock(Filter.class);
String filterString = "(objectClass=" + DumpContributor.class.getName() + ")";
expect(context.createFilter(filterString)).andReturn(filter);
context.addServiceListener((ServiceListener)anyObject(), eq(filterString));
expect(context.getServiceReferences(DumpContributor.class.getName(), null)).andReturn(new ServiceReference[0]).atLeastOnce();
ServiceRegistration generatorRegistration = createMock(ServiceRegistration.class);
ServiceRegistration summaryRegistration = createMock(ServiceRegistration.class);
ServiceRegistration jmxRegistration = createMock(ServiceRegistration.class);
ServiceRegistration threadRegistration = createMock(ServiceRegistration.class);
ServiceRegistration heapRegistration = createMock(ServiceRegistration.class);
expect(context.registerService(eq(DumpGenerator.class.getName()), isA(StandardDumpGenerator.class), (Dictionary<?,?>)isNull())).andReturn(generatorRegistration);
expect(context.registerService(eq(DumpContributor.class.getName()), isA(SummaryDumpContributor.class), (Dictionary<?,?>)isNull())).andReturn(summaryRegistration);
expect(context.registerService(eq(DumpContributor.class.getName()), isA(JmxDumpContributor.class), (Dictionary<?,?>)isNull())).andReturn(jmxRegistration);
expect(context.registerService(eq(DumpContributor.class.getName()), isA(ThreadDumpContributor.class), (Dictionary<?,?>)isNull())).andReturn(threadRegistration);
expect(context.registerService(eq(DumpContributor.class.getName()), isA(HeapDumpContributor.class), (Dictionary<?,?>)isNull())).andReturn(heapRegistration);
generatorRegistration.unregister();
summaryRegistration.unregister();
jmxRegistration.unregister();
threadRegistration.unregister();
heapRegistration.unregister();
context.removeServiceListener((ServiceListener)anyObject());
replay(context, filter, generatorRegistration, summaryRegistration, jmxRegistration, threadRegistration, heapRegistration);
bundleActivator.start(context);
bundleActivator.stop(context);
verify(context, filter, generatorRegistration, summaryRegistration, jmxRegistration, threadRegistration, heapRegistration);
}
Creating a set of test stubs is a delicate balancing act, especially when it comes to an API as complex as the OSGi Framework. On one hand, you need the implementation to be simple enough that you are not likely to introduce bugs and you can allow the user to specify the behavior and return values of the calls. On the other hand, you need a sophisticated enough implementation that complex objects (such as the ServiceTracker) can get expected behavior when they call the stub.
With all this in mind, I set off implementing test stubs for BundleContext, Bundle, ServiceReference, and ServiceRegistration. To get an idea of what kind of difference these test stubs make, here's the previous test after converting it to use the stubs.
@Test
public void startAndStop() throws Exception {
BundleActivator bundleActivator = new DumpBundleActivator();
StubBundleContext bundleContext = new StubBundleContext().addFilter(new ObjectClassFilter(DumpContributor.class));
bundleActivator.start(bundleContext);
assertServiceListenerCount(bundleContext, 1);
assertServiceRegistrationCount(bundleContext, DumpGenerator.class, 1);
assertServiceRegistrationCount(bundleContext, DumpContributor.class, 4);
bundleActivator.stop(bundleContext);
assertCleanState(bundleContext);
}
As you can see this test is now much simpler to read and maintain, but most importantly it's more understandable. The basic building block of this test is the StubBundleContext. This context is passed into the DumpBundleActivator's start call where services are registered. But it's the assertions where things really get interesting.
Using the StubBundleContext, it is possible for the user to assert everything they'd need to for testing. However, the test stubs package also includes an OSGiAssert class that makes typical assertions more readable. In this case, you can see that after calling start we want to have one ServiceListener registered, one DumpGenerator service registered, and four DumpContributor services registered. After calling stop we want to make sure that everything is cleaned up and the system is left in a clean state.