Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Section
Column
width80%

The JUnit4OSGi framework: a simple test framework for OSGi

Why Junit4OSGi ?

Junit4OSGi is a framework allowing the execution of JUnit tests in an OSGi environment. It stems from the fact that testing applications on top on OSGi is quite complex compared to classic Java unitary tests

The goal of JUnit4OSGi is to provide the same mechanisms as unitary tests in an OSGi context. It allows using the JUnit concepts, although tests are run in an OSGi framework, using all of its distinctivenesses. The JUnit4OSGi bundle allows you to :

  • Perform OSGi services tests.
  • Test applications on different VMs, OSGi implementations.
  • Test dynamism impact.

This pages describes also a tutorial. With the archive tutorial, a preconfigured version of Felix (with the test framework already installed) is provided.

Working principles

The goal of unitary tests in an OSGi context is to check that the bundles (the OSGi deployment units) to test are correctly deployed, that they expose the expected services and that these services work properly. Tests are placed in a bundle, which is deployed in the OSGi environment to test. Then, they can interact with the environment (more specifically bundles and services) to check that everything works well.

How to run test cases

Once tests have been deployed on the OSGi framework, there are several way to run the tests

  • You can use the JUnit4OSGi immediate-runner, which run tests contained in all the test-bundles. The results are displayed in the framework console output.
  • The JUnit4OSGi Felix Command (available only for the Apache Felix OSGi framework), allows you to run tests contained in a specific bundle. Test results are also displayed in the framework console output. The syntax of the command is very simple :
    • To run tests contained in the bundle with the given id :{{
      No Format
      junit <bundle id>
      }}
    • To run tests contained in all the test-bundles installed in the framework :junit all
  • The JUnit4OSGi Swing GUI lets you select the test cases and test suites to execute and shows you graphically the result of the tests. You can double-click on a test case result to show its details.

TestCase, TestSuite

A test case is an environment (made of conditions and variables) under which a tester will check that a requirement is satisfied. For JUnit, test cases are classes that define several test methods ; each test method tests an aspect of the targeted requirement. Test cases are often collected into test suites. A test suite aggregates several test cases (and even other test suites), including the notion of test hierarchy. Tests can be organized according to the different requirements they try to validate. The skeleton of a JUnit TestCase and TestSuite is shown just above :

Code Block
/**
 * The skeleton of a JUnit test case
 */
public class MyTestCase extends junit.framework.TestCase {
    ...

 public void setUp() {
    // Performs actions BEFORE running any test case.
 }

 public void testSomething() throws AnyException {
    // A test method
    ...
    assertTrue(myTest);
    assertEquals(myValue, expectedValue);
    ...
 }

 public void testAnotherThing() {
    // Another test method
    ...
 }


 public void tearDown() {
    // Performs actions AFTER running all test cases.
 }
}
Code Block
/**
 * The skeleton of a JUnit test suite
 */
public class MyTestSuite extends junit.framework.TestSuite {
 /**
 * The skeleton of a JUnit test suite
 */
 public static Test suite() {
    TestSuite suite = new TestSuite("The name of the test suite");
    suite.addTestSuite(MyTestCase.class);
    suite.addTestSuite(AnotherTestCase.class);
    ...
    return suite;
 }
}

First, tests methods are declared in the test case. Their name must begin with "test", so JUnit will execute them on demand. The test are expressed in terms of JUnit assertions ; the assert*() methods causes JUnit test failure if the given assertion is false. The setUp() and tearDown() methods perform specific actions before and after test cases are run. Then, the test suite collect various test cases. It must implement the suite() method that returns the global (and organized) test suite to JUnit.

OSGiTestCase and OSGiTestSuite

An OSGi test case, is a test case that runs in an OSGi context. OSGiTestCase is the class that all the OSGi test cases you write must extend. This class is a kind of bridge between the JUnit TestCase class and the OSGi environment. The only thing the OSGiTestCase class adds is the access to the bundle context of the bundle containing tests, and some utility methods, giving an easy access to other bundles and services registered in the OSGi framework.

The structure of an OSGiTestCase is exactly the same as a classic JUnit TestCase :

Code Block
 /**
  * The structure of an OSGiTestCase
  */
 public class MyOSGiTestCase extends OSGiTestCase {
    public void setUp() {...}
    public void testSomething() {
        // You can access here the bundle context through 
        // the 'context' protected field
        context.getServiceReference(...);
        ...
    }
 public void testAnotherThing() {...}
 ...
 public void tearDown() {...}
 }

By extension, OSGi test suites are collections of OSGi test cases. But you can also add classic JUnit test cases inside your OSGiTestSuite. The skeleton of an OSGiTestSuite is globally the same as a TestSuite, except the fact that a reference to the bundle context is passed :

Code Block
public class MyOSGiTestSuite {
 /**
  * This method returns the suite of tests to run.
 */
 public static Test suite(BundleContext bc) {
    OSGiTestSuite suite = new OSGiTestSuite("My OSGi test suite", bc);
    suite.addTestSuite(MyFirstTest.class);
    suite.addTestSuite(MySecondTest.class);
    ...
    // Here, we add a sub test suite in this test suite.
    suite.addTest(AnotherTestSuite.suite(bc));
    ...
    return suite;
 }
}

How to declare test suites

This section explains how to declare your test suites in order to expose them to the JUnit4OSGi bundle.

The written OSGi test suites must be declared by the bundle containing them. To do so, you define add the Test-Suite property in your bundle's header. The following snippets show you how to configure your bundle generation tool to add this property in the header. You can even declare test cases in it. The Junit4OSGi bundle will detect such an header in installed bundle (using the extender pattern) and execute contained tests on demand.

With the maven-bundle-plugin, add the following lines in your project's pom :

Code Block
xml
xml
<plugin>
  <groupId>org.apache.felix</groupId>
  <artifactId>maven-bundle-plugin</artifactId>
  ...
  <extensions>true</extensions>
  <configuration>
  <instructions>
     ...
      <!-- Declare here the test cases and test suites of your bundle -->
      <Test-Suite>
       a.package.MyFirstTestSuite,
       yet.another.package.MySingleTestCase
       ...
      </Test-Suite>
  </instructions>
  </configuration>
</plugin>

With the aQute Bnd Ant task, add the following lines in your project bnd file:

Code Block
Test-Suite: a.package.MyFirstTestSuite, yet.another.package.MySingleTestCase, ...

Quick examples

The following examples show you how to perform unitary tests on your OSGi platform. The first example recovers the example given in the JUnit Cookbook, "bundlizes" it so tests can be run in an OSGi environment. None of the JUnit4OSGi specific features is used, but it shows how to adapt classic JUnit tests. The second example is more OSGi-oriented, and shows how a unitary test can access to the framework via its bundle context.

These examples can be downloaded here.

Bundles to deploy to use junit4osgi

If you don't use the archive, you can deploy the junit4osgi framework manually. Here is the list of the bundles to deploy and start:

  • org.apache.felix.ipojo-1.1.0-SNAPSHOT.jar: iPOJO Core bundle
  • org.apache.felix.ipojo.handler.extender-1.1.0-SNAPSHOT.jar: iPOJO Extender pattern handler
  • org.apache.felix.ipojo.junit4osgi-1.1.0-SNAPSHOT.jar: the junit4osgi framework
  • org.apache.felix.ipojo.junit4osgi.felix-command-1.1.0-SNAPSHOT.jar: the command line junit4osgi runner

The remixed JUnit example

This example is a simple conversion of a classic JUnit example derived from the JUnit Cookbook. The test case and the test suite are shown to remind you JUnit principles.

Code Block
package junit.example;
import junit.framework.TestCase;
import junit.money.Money;

public class SimpleTestCase extends TestCase {
    private Money f12CHF;
    private Money f14CHF;
    public void setUp() {
        f12CHF= new Money(12, "CHF");
		f14CHF= new Money(14, "CHF");
	}
	public void testEquals() {
		assertTrue(!f12CHF.equals(null));
		assertEquals(f12CHF, f12CHF);
		assertEquals(f12CHF, new Money(12, "CHF"));
		assertTrue(!f12CHF.equals(f14CHF));
	}
	public void testSimpleAdd() {
		Money expected= new Money(26, "CHF");
		Money result= f12CHF.add(f14CHF);
		assertTrue(expected.equals(result));
	}
}
Code Block
package junit.example;
import junit.framework.Test;
import junit.framework.TestSuite;
public class SimpleTestSuite {
	public static Test suite() {
		TestSuite suite = new TestSuite("Money Simple Test Suite");
		suite.addTestSuite(SimpleTestCase.class);
		return suite;
	}
}

The following bnd file declares the test suite in the target bundle's header :

Code Block
Private-Package: junit.money, junit.example
Test-Suite: junit.example.SimpleTestSuite

Once built, the bundle must be deployed in the provided Felix framework, and tests can be performed using the 'junit' command :

Code Block
	-> ps
	START LEVEL 1
	ID State Level Name
	...
	[ 12] [Active ] [ 1] Junit-Example (0)
	...
	-> junit 12
	Executing [Money Simple Test Suite]
	..
	Time: 0
	OK (2 tests)
->

As you can see above, all tests have been correctly executed !

An OSGi-based JUnit example

This example shows you how to interact with the OSGi framework within your tests. The test bundle provide a service (HelloService) and tests its work normally. To get the service reference of the HelloService, it uses the bundle context field of the OSGiTestCase class (named 'context') and interacts with it like any other OSGi bundle does.

Code Block
package junit.example;

import junit.service.hello.HelloService;

import org.apache.felix.ipojo.junit4osgi.OSGiTestCase;
import org.osgi.framework.ServiceReference;

public class SimpleTestCase extends OSGiTestCase {
    
    public void testHelloAvailability() {
        ServiceReference ref = context.getServiceReference(HelloService.class.getName());
        assertNotNull("Assert Availability", ref);
    }
    
    public void testHelloAvailability2() {
        ServiceReference ref = getServiceReference(HelloService.class.getName(), null);
        assertNotNull("Assert Availability", ref);
    }
    
    public void testHelloMessage() {
        ServiceReference ref = context.getServiceReference(HelloService.class.getName());
        assertNotNull("Assert Availability", ref);
        HelloService hs = (HelloService) context.getService(ref);
        String message = hs.getHelloMessage();
        assertNotNull("Check the message existence", message);
        assertEquals("Check the message", "hello", message);
    }
    
    public void testHelloMessage2() {
    	assertTrue("Check availability of the service", 
    	    isServiceAvailable(HelloService.class.getName()));
        HelloService hs = (HelloService) getServiceObject(HelloService.class.getName(), null);
        String message = hs.getHelloMessage();
        assertNotNull("Check the message existence", message);
        assertEquals("Check the message", "hello", message);
    }
}

The performed tests give out the following results :

Code Block
-> ps
START LEVEL 1
ID State Level Name
...
[ 17] [Active ] [ 1] Junit-OSGi-Example (0)
...
-> services 17
Junit-OSGi-Example (17) provides:
---------------------------------
objectClass = junit.service.hello.HelloService
service.id = 36
-> junit 17
Executing [Hello Service Test Suite]
..
Time: 0,015
OK (4 tests)
->

Utility methods description

The OSGiTestCase class provides some utility methods which eases the use of OSGi services and the interaction with iPOJO components. The description of these methods can be found afterwards.

On top of the presented (static) methods, you can find non-static methods which have access the local bundle context only.

OSGi specific methods

  • Object getServiceObject(Bundle bundle, String itf, String filter) : this method returns the service object of a service provided by the specified bundle, offering the specified interface and matching the given filter.
  • Object[] getServiceObjects(Bundle bundle, String itf, String filter) : returns the service objects of the all the services provided by the specified bundle, offering the specified interface and matching the given filter.
  • ServiceReference getServiceReference(Bundle bundle, String itf, String filter) : returns the service reference of a service provided by the specified bundle, offering the specified interface and matching the given filter.
  • ServiceReference getServiceReferenceByPID(Bundle bundle, String itf, String pid) : returns the service reference of the service provided by the specified bundle, offering the specified interface and having the given persistent ID.
  • ServiceReference getServiceReferenceByName(Bundle bundle, String itf, String name) : returns the service reference of a service provided by the specified bundle, offering the specified interface and having the given name.
  • ServiceReference[] getServiceReferences(Bundle bundle, String itf, String filter) : returns the service reference of all the services provided in the specified bundle, offering the specified interface and matching the given filter.
  • boolean isServiceAvailable(String itf) : returns true if the service is available.
  • boolean isServiceAvailableByName(String itf, String name) : returns true if the service is available with the given name.
  • boolean isServiceAvailableByPID(String itf, String pid) : returns true if the service is available with the given pid.

These methods can be used directly in the OSGi Test Case as illustrated above.

To get service objects, use the getServiceObject methods (getServiceObject(String itf, String filter), getServiceObject(ServiceReference ref) and getServiceObjects(String itf, String filter)). Then services are automatically 'unget' during the tearDown method.

iPOJO specific methods

As JUnit4OSGi is part of the iPOJO project, it contains iPOJO specific utility methods, to help accessing factories and the creation of component instances.

To access to these methods, creates a IPOJOHelper object:

Code Block
public class ATestCase extends OSGiTestCase {

    IPOJOHelper helper = new IPOJOHelper(this);
}

With this object, you can easily retrieve data about instances, as well as recreated new instances, analyzing service contexts...
Moreover, if you create instances with the non-static methods (createComponentInstance(String, String, Dictionary), createComponentInstance(String) or
createComponentInstance(String, String), to dispose instances just call the helper.dispose() method. The created instances are automatically disposed.

Configuration Admin methods

Coming soon...

Column
width20%
Include Page
FELIX:apache-felix-ipojo-menu
FELIX:apache-felix-ipojo-menu