blob: 8fd0ed14d52562f8c5c9a9c75c6195de97d8df93 [file] [log] [blame]
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE preface PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
<chapter id="testing">
<title>Testing OSGi based Applications</title>
<para>By following best practices and using the Gemini Blueprint
support, your bean classes should be easy to unit test as they will have
no hard dependencies on OSGi, and the few OSGi APIs that you may interact
with (such as <interfacename>BundleContext</interfacename>) are interface-based
and easy to mock. Whether you want to do unit testing or <ulink url="">
integration</ulink> testing, Spring DM can ease your task.</para>
<section id="testing:mocks">
<title>OSGi Mocks</title>
<title>Mocks vs Stubs</title>
<para>There are various strategies to unit test code
that requires collaborators. The two most popular strategies
are <emphasis>stubs</emphasis> and <emphasis>mocks</emphasis>.
See <ulink url="">
this</ulink> article by Martin Fowler which describes in detail the difference
between them.</para>
Even though most OSGi API are interfaces and creating mocks using a specialized
library like <ulink url="">EasyMock</ulink> is fairly
simple, in practice the amount of code of setting the code (especially on JDK 1.4)
becomes cumbersome. To keep the tests short and concise, Gemini Blueprint/Spring DM provides OSGi
mocks under <literal>org.eclipse.gemini.blueprint.mock</literal> package.</para>
<para>It's up to you to decide whether they are useful or not however, we make extensive
use of them inside Gemini Blueprint/Spring DM test suite. Below you can find a code snippet that you
are likely to encounter in our code base:</para>
<programlisting language="java"><![CDATA[private ServiceReference reference;
private BundleContext bundleContext;
private Object service;
protected void setUp() throws Exception {
reference = new MockServiceReference();
bundleContext = new MockBundleContext() {
public ServiceReference getServiceReference(String clazz) {
return reference;
public ServiceReference[] getServiceReferences(String clazz, String filter)
throws InvalidSyntaxException {
return new ServiceReference[] { reference };
public Object getService(ServiceReference ref) {
if (reference == ref)
return service;
public void testComponent() throws Exception {
OsgiComponent comp = new OsgiComponent(bundleContext);
assertSame(reference, comp.getReference());
assertSame(object, comp.getTarget());
<para>As ending words, experiment with them and choose whatever style or library you feel most confortable with.
In our test suite we use the aforementioned mocks, EasyMock library and plenty of integration testing (see below).</para>
<section id="testing:integration">
<title>Integration Testing</title>
<title>What about JUnit4/TestNG?</title>
<para>While JUnit4/TestNG overcome the class inheritance
problem that appears when building base JUnit classes, by decoupling
the runner from the test through annotations, Gemini Blueprint/Spring DM cannot use
them since it has to support Java 1.4.</para>
<para>However, it is planned for the future to provide an optional,
JVM 5-based testing extension to integrate the existing testing framework
with the aforementioned libraries.
<para>In a restricted environment such as OSGi, it's important to test the visibility and versioning of your classes,
the manifests or how your bundles interact with each other (just to name a few).</para>
<para> To ease integration testing, the Gemini Blueprint project provides a test class hierarchy
(based on <classname>org.eclipse.gemini.blueprint.test.AbstractOsgiTests</classname>) that
provides support for writing regular <literal>JUnit</literal> test cases that are then
automatically executed in an OSGi environment.</para>
<para>In general, the scenario supported by Gemini Blueprint/Spring DM testing framework is:</para>
<para>start the OSGi framework (Equinox, Knopflerfish, Felix)</para>
<para>install and start any specified bundles required for the
<para>package the test case itself into a <literal>on the fly</literal> bundle,
generate the manifest (if none is provided) and install it in the OSGi
<para>execute the test case inside the OSGi framework</para>
<para>shut down the framework</para>
<para>passes the test results back to the originating test case
instance that is running outside of OSGi</para>
<warning>The testing framework is aimed at running OSGi integration tests from a non-OSGi environment (like Ant/Maven/IDE). The testing framework
is NOT meant to be used as an OSGi bundle (nor will it work for that matter). In practice this means that the testing bundle should be separate from the bundle(s) it
tests (similar to unit testing, where tests are separate from the classes they test).</warning>
<para>By following this sequence it is trivial to write JUnit-based
integration tests for OSGi and have them integration into any environment
(IDE, build (ant, maven), etc.) that can work with JUnit.</para>
<para>The rest of this chapter details (with examples) the features
offered by Gemini Blueprint/Spring DM testing suite.</para>
<section id="testing:integration:simple-test">
<title>Creating A Simple OSGi Integration Test</title>
While the testing framework contains several classes that offer specific
features, it is most likely that your test cases will extend
<classname>org.eclipse.gemini.blueprint.test.AbstractConfigurableBundleCreatorTests</classname> (at least
this is what we use in practice).
<para>Let's extend this class and interact with the OSGi platform through
the <literal>bundleContext</literal> field:</para>
<programlisting language="java"><![CDATA[public class SimpleOsgiTest extends AbstractConfigurableBundleCreatorTests {
public void testOsgiPlatformStarts() throws Exception {
<para>Simply execute the test as you normally do with any JUnit test. On Equinox 3.2.x,
the output is similar to:</para>
<remark>It is likely that you will see different log statements made by the testing
framework during your own test execution, but these can be disabled as they only have an informative
value and do not affect the actual execution.</remark>
<para>Note that you did not have to create any bundle, write any MANIFEST or
bother with imports or exports, let alone starting and shutting down the OSGi
platform. The testing framework takes care of these automatically
when the test is executed.</para>
<para>Let's do some quering and figure out what the environment in which the tests run is.
A simple way to do that is to query the <interfacename>BundleContext</interfacename>
for the installed bundles:</para>
<programlisting language="java"><![CDATA[public void testOsgiEnvironment() throws Exception {
Bundle[] bundles = bundleContext.getBundles();
for (int i = 0; i < bundles.length; i++) {
System.out.print(", ");
<para>The output should be similar to:</para>
<programlisting><![CDATA[OSGi System Bundle, ObjectWeb ASM, log4j.osgi, spring-test, spring-osgi-test, spring-osgi-core,
spring-aop, spring-osgi-io, slf4j-api,
spring-osgi-extender, etc... TestBundle-testOsgiPlatformStarts-com.your.package.SimpleOsgiTest,
<para>As you can see, the testing framework installs the mandatory requirements required for running the
test such as the Spring, Gemini Blueprint/Spring DM, slf4j jars among others.</para>
<section id="testing:integration:provisioning">
<title>Installing Test Prerequisites</title>
<title>OSGi-friendly libraries</title>
<para>To work in OSGi environments, jars need to declare in their
<literal>MANIFEST.MF</literal>, Export or Import packages; that is
declare what classes they need or offer to other bundles.
Most libraries are OSGi unaware and do not provide the proper manifest
entries which means they are unusable in an OSGi environment.</para>
<para>At the moment, there are several initiatives in the open source space
to provide the proper manifest - please see the FAQ for more information.
<para>Besides the Gemini Blueprint/Spring DM jars and the test itself is highly likely that you depend on
several libraries or your own code for the integration test.</para>
<para>Consider the following test that relies on Apache Commons
<ulink url="">Lang</ulink>:</para>
<programlisting language="java"><![CDATA[import org.apache.commons.lang.time.DateFormatUtils;
public void testCommonsLangDateFormat() throws Exception {
System.out.println(DateFormatUtils.format(new Date(), "HH:mm:ssZZ"));
<para>Running the test however yields an exception:</para>
<programlisting><![CDATA[java.lang.IllegalStateException: Unable to dynamically start generated unit test bundle
Caused by: org.osgi.framework.BundleException: The bundle could not be resolved.
Reason: Missing Constraint: Import-Package: org.apache.commons.lang.time; version="0.0.0"
... 15 more
<para>The test requires <literal>org.apache.commons.lang.time</literal> package but there is no bundle
that exports it. Let's fix this by installing a commons-lang bundle (make sure you use at least version 2.4
which adds the proper OSGi entries to the jar manifest).</para>
<para>One can specify the bundles that she wants
to be installed using <literal>getTestBundlesNames</literal> or <literal>getTestBundles
</literal> method. The first one returns an array of String that indicate the bundle
name, package and versioning through as a String while the latter returns an array of
<literal>Resource</literal>s that can be used directly for installing the bundles.
That is, use <literal>getTestBundlesNames</literal> when you rely on somebody else to locate
(the most common case) the bundles and <literal>getTestBundles</literal> when you want to
locate the bundles yourself.
<para>By default, the test suite performs a lookup for artifacts, similar to the one used by
<ulink url="">Maven2</ulink>, searching first the items as being relative to the
current project and then falling back to the local repository.
The locator expects the bundle String to be a comma separated values containing the artifact group, name, version and (optionally) type.
It's likely that in the future, various other locators will be available. One can plug in their own
locator through the <interfacename>org.eclipse.gemini.blueprint.test.provisioning.ArtifactLocator</interfacename> interface.
<para>Let's fix our integration test by installing the required bundle (and some extra osgi libraries):</para>
<programlisting language="java"><![CDATA[protected String[] getTestBundlesNames() {
return new String[] {
"javax.transaction, com.springsource.javax.transaction, 1.1.0",
"commons-lang, commons-lang, 2.4" };
<para>Rerunning the test should show that these bundles are now installed in the OSGi platform.</para>
<note>The artifacts mentioned above have to exist in your local maven repository.</note>
<section id="testing:integration:advanced-topics">
<title>Advanced Testing Framework Topics</title>
<para>The testing framework allows a lot of customization to be made. This chapter
details some of the existing hooks that you might want to know about. However, these
are advanced topics as they increase the complexity of your test infrastructure.
<section id="testing:integration:customize-manifest">
<title>Customizing The Test Manifest</title>
<para>There are cases where the auto-generated test manifest does not suit the needs of the test.
For example the manifest requires some different headers or a certain package needs to be an optional import.</para>
For simple cases, one can work directly with the generated manifest - in the example below, the bundle class path
is being specified:
<programlisting language="java"><![CDATA[protected Manifest getManifest() {
// let the testing framework create/load the manifest
Manifest mf = super.getManifest();
// add Bundle-Classpath:
mf.getMainAttributes().putValue(Constants.BUNDLE_CLASSPATH, ".,bundleclasspath/simple.jar");
return mf;
Another alternative is to provide your own manifest by overriding <literal>getManifestLocations()</literal>:</para>
<programlisting language="java"><![CDATA[protected String getManifestLocation() {
return "classpath:com/xyz/abc/test/MyTestTest.MF";
<para>However each manifest needs the following entry:</para>
<quote>Bundle-Activator: org.eclipse.gemini.blueprint.test.JUnitTestActivator</quote>
<para>since without it, the testing infrastructure cannot function properly. Also, one needs to
import JUnit, Spring and Gemini Blueprint/Spring DM specific packages used by the base test suite:</para>
<programlisting><![CDATA[Import-Package: junit.framework,
<para>Failing to import a package used by the test class will cause the test to fail with a
<literal>NoDefClassFoundError</literal> error.</para>
<section id="testing:integration:specify-test-jar-content">
<title>Customizing Test Bundle Content</title>
<para>By default, for the on-the-fly bundle, the testing infrastructure uses all the classes, xml and properties files
found under <literal>./target/test-classes</literal> folder. This matches the project layout for maven which is used
(at the moment by Gemini Blueprint/Spring DM). These settings can be configured in two ways:</para>
<listitem><para>programmatically by overriding <classname>AbstractConfigurableBundleCreatorTests</classname> <literal>getXXX</literal>
<listitem><para>declaratively by creating a properties file having a similar name with the test case. For example, test
<literal></literal> will have the properties file named <literal>com/xyz/</literal>.
If found, the following properties will be read from the file:</para>
<table id ="integration-test-jar-setting-file" border="1" pgwide="1">
<title>Default test jar content settings</title>
<tgroup cols="3">
<entry>Property Name</entry>
<entry>Default Value</entry>
<entry>the root folder considered as the jar root</entry>
<entry>Comma-separated string of Ant-style patterns</entry>
<entry>manifest location given as a String. By default it's empty meaning the manifest
will be created by the test framework rather then being supplied by the user.</entry>
<para>This option is handy when creating specific tests that need to include certain resources (such as localization files
or images).</para>
<para>Please consult <classname>AbstractConfigurableBundleCreatorTests</classname> and
<classname>AbstractOnTheFlyBundleCreatorTests</classname> tests for more customization hooks.
<section id="testing:integration:understanding-manifest-creator">
<title>Understanding The <code>MANIFEST.MF</code> Generation</title>
<para>A useful feature of the testing framework represents the automatic creation of the test manifest based on the test bundle content. The manifest
creator component uses byte-code analysis to determine the packages imported by the test classes so that it can generate the proper OSGi directives for them.
Since the generated bundle is used for running a test, the creator will use the following assumptions:</para>
<listitem><para>No packages will be exported.</para> <para>The <emphasis>on-the-fly</emphasis> bundle is used for running a test which
(usually) consumes OSGi packages for its execution. This behaviour can be changed by <link linkend="testing:integration:customize-manifest">customizing</link>
the manifest.</para>
<listitem><para>Split packages (i.e. classes from the same package can come from different bundles) are not supported.</para>
This means that packages present in the test framework are considered complete and no <code>Import-Package</code> entry will be generated for them.
To avoid this problem, consider using sub-packages or moving the classes inside one bundle. Note that split packages are discouraged due to the
issues associated with them (see the OSGi Core spec, Chapter 3.13 - Required Bundles).</para>
<para>The test bundle contains only test classes.</para>
<para>The byte-code parser will look only at the test classes hierarchy. Any other class included in the bundle, will not be considered so no imports
will be generated for it. To change the default behaviour, override <methodname>createManifestOnlyFromTestClass</methodname> to return
<programlisting language="java"><![CDATA[protected boolean createManifestOnlyFromTestClass() {
return false;
<note>The time required to generate the manifest might increase depending on the number and size of classes in the bundle.</note>
Additionally consider customizing the manifest yourself or attaching the extra code as inner classes to the test class (so it gets parsed automatically).</para>
The reason behind <emphasis>the lack of such features</emphasis> is the byte-code parser is aimed to be simple and fast at creating test manifests -
it is not meant as a general-purpose tool for creating OSGi artifacts.
<!-- end of advanced topics -->
<section id="testing:integration:appContext">
<title>Creating An OSGi Application Context</title>
<para>Gemini Blueprint/Spring DM testing suite builds on top of Spring testing classes. To create an application context
(OSGi specific), one should just override <literal>getConfigLocations[]</literal> method and indicate
the location of the application context configuration. At runtime, an OSGi application context will be created
and cached for the lifetime of the test case.</para>
<programlisting language="java"><![CDATA[protected String[] getConfigLocations() {
return new String[] { "/com/xyz/abc/test/MyTestContext.xml" };
<section id="testing:integration:specify-platform">
<title>Specifying The OSGi Platform To Use</title>
<para>The testing framework supports out of the box, three OSGi 4.0 implementations namely:
Equinox, Knopflerfish and Felix. To be used, these should be in the test classpath. By default,
the testing framework will try to use Equinox platform. This can be configured in several ways:</para>
<listitem><para>programmatically through <literal>getPlatformName()</literal> method</para>.
<para>Override the aforementioned method and indicate the fully qualified name of
the <interfacename>Platform</interfacename> interface implementation. Users can use the <classname>Platforms</classname>
class to specify one of the supported platforms:</para>
<programlisting language="java"><![CDATA[protected String getPlatformName() {
return Platforms.FELIX;
through <literal>org.eclipse.gemini.blueprint.test.framework</literal> system property.</para>
<para>If this property is set,
the testing framework will use its value as a fully qualified name of a Platform implementation.
It that fails, it will fall back to Equinox after logging a warning message.
This option is useful for building tools (such as ant or maven) since it indicates a certain
target environment without changing and test code.</para>
<section id="testing:integration:specify-test-wait-time">
<title>Waiting For The Test Dependencies</title>
<para>A built-in feature of the testing framework is the ability to wait until all dependencies are deployed before
starting the test execution. Since the OSGi platforms are concurrent by nature, installing a bundle doesn't mean that
all its services are running. By running a test before its dependency services are fully initialized can cause sporadic
errors that pollute the test results. By default, the testing framework inspects all bundles installed by the user and,
if they are Spring-powered bundles, waits until they are fully started (that is their application context is published
as an OSGi service). This behaviour can be disabled by overriding <literal>shouldWaitForSpringBundlesContextCreation</literal>
method. Consult <classname>AbstractSynchronizedOsgiTests</classname> for more details.</para>
<section id="testing:integration:performance">
<title>Testing Framework Performance</title>
<para>Considering all the functionality offered by the testing framework, one might wonder if this doesn't become a
performance bottleneck. First, it's worth noting that all the work done automatically by the testing infrastructure
has to be done anyway (such as creating the manifest or creating a bundle for the test or installing the bundles).
Doing it manually simply does not work as it's too error prone and time consuming.
In fact, the current infrastructure started as way to do efficient, automatic testing without worrying
about deployment problems and redundancy.</para>
<para>As for the numbers, the current infrastructure has been used internally for the last half a year - our integration tests
(around 120) run in about 3:30 minutes on a laptop. Most of this time is spent on starting and stopping the OSGi platform: the "testing
framework" takes around 10% (as shown in our profiling so far).
For example, the manifest generation has proved to take less then 0.5 seconds in general, while the jar creation around 1
<para>However, we are working on making it even faster and smarter so that less configuration options are needed and
the contextual information available in your tests is used as much as possible. If you have any ideas or suggestion,
feel free to use our issue tracker or/and forum.
<para>Hopefully this chapter showed how Gemini Blueprint/Spring DM testing infrastructure can simplify OSGi integration testing and
how it can be customized. Consider consulting the javadocs for more information.