Getting started with JUnit

Getting started with JUnit

Unknown macro: {scrollbar}

Basics

The following will guide you briefly through the steps os writing integration tests making use of JUnit and Pax Exam. Pax Exam will not interfere with the way you use to write JUnit tests but you will have to configure your unit tests to make make use of Pax Exam.
This guide assumes that you know what unit testing is and how JUnit4 (we always refer to version 4 when mentioning JUnit in this article) works in particular. If that is not the case, there are plenty of guides available on the internet. A quick startup you may find in JUnit Cookbook.

Unit Tests vs. Integration Tests

Usually your write the units tests alongside with the classes (source code) you are about to test. With Pax Exam you can write integration tests where best practice is to write them separated from your junit tests. This is mainly because you do not want that your test code to interfere with your bundle. Another reason that in integration tests you test on a highler level. We say "you test the orchestration of classes/components".

To startup you will have to make the Pax Exam artifacts available in your class path.

Basic test

You will start writing your usual JUnit test:

import org.junit.Test;

public class MyJUnitTest
{

    @Test
    public void testMethod()
    {

    }

}

As you can see we are using the semantics of JUnit4 meaning that there is no need to subclass any Junit nor Pax Exam class and you will have to mark your test methods using @Test annotation.

Mark the test to be run by Pax Exam

Next you will have to instruct JUnit to run the test using the "special" test runner that comes along with Pax Exam. This runner will run your test according to the described workflow.
You do this by adding the @RunWith class annotation:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.junit.JUnit4TestRunner;

@RunWith( JUnit4TestRunner.class )
public class MyJUnitTest
{

    @Test
    public void testMethod()
    {

    }

}

At this moment the tests will be run inside an OSGi framework. The OSGi framework it will be used is the default one (Felix), using default configuration, as there are not yet any configuration specified.

Make use of OSGi context

Even if the test above is a valid JUnit test that by making use of Pax Exam runs inside an OSGi framework, the test does not make to much sense as it has no means of interacting with the OSGi framework the test runs within. To fulfill this need Pax Exam can inject you a BundleContext. You can archive this in two ways (which one you prefer).

Inject bundle context into a class field
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.osgi.framework.BundleContext;
import org.ops4j.pax.exam.Inject;
import org.ops4j.pax.exam.junit.JUnit4TestRunner;

@RunWith( JUnit4TestRunner.class )
public class MyJUnitTest
{

    @Inject
    private BundleContext bundleContext;

    @Test
    public void testMethod()
    {
        assertThat( bundleContext, is( notNullValue() ) );
    }

}

What matters is the Annotation plus the field type (BundleContext). Name can be selected as wanted.

Inject bundle context into method parameter
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.osgi.framework.BundleContext;
import org.ops4j.pax.exam.junit.JUnit4TestRunner;

@RunWith( JUnit4TestRunner.class )
public class MyJUnitTest
{

    @Test
    public void testMethod( final BundleContext bundleContext )
    {
        assertThat( bundleContext, is( notNullValue() ) );
    }

}

Now you can make use of the methods provided by BundleContext to get services, start/stop bundles and so on in order to fulfill your test needs.

Adding configuration

The above setup - even if it will allow you to run OSGi unit tests - will most likely satisfy only trivial unit test. For more advanced tests there should be a way to configure several aspects of the OSGi framework the test will run within. This kind of aspects being the OSGi framework implementation (Felix, Equinox, Knopflerfish), specific version of the OSGi framework, provisioned bundles (bundles to be installed alongside the test bundle), system properties, boot delegation packages, ...
In order to do this you will have to specify this options by making use of a configuration method:

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

@RunWith( JUnit4TestRunner.class )
public class MyJUnitTest
{

    @Inject
    private BundleContext bundleContext;

    @Configuration
    public static Option[] configuration()
    {
        return options(
        );
    }

    @Test
    public void testMethod()
    {
        assertThat( bundleContext, is( notNullValue() ) );
    }

}

As you can see what you have to do is to add a method (that can be named as you want) and mark it with a @Configuration annotation. This method should be public and static and return an array of options that fulfills your needs. To make writing tests easier, an utility class can be used CoreOptions, that provides easy to use methods for configuring divers aspects of the OSGi framework. You may wanna import statically this methods in order to have a fluent api like usage (import static org.ops4j.pax.exam.CoreOptions.*;). Here is an example of usage:

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

@RunWith( JUnit4TestRunner.class )
public class MyJUnitTest
{

    @Inject
    private BundleContext bundleContext;

    @Configuration
    public static Option[] configuration()
    {
        return options(
            frameworks(
                felix().version( "1.0.4" )
            ),
            systemProperty( "foo" ).value( "bar" ),
            bootDelegationPackages(
                "sun.*",
                "com.sun.*"
            )
        );
    }

    @Test
    public void testMethod()
    {
        assertThat( bundleContext, is( notNullValue() ) );
    }

}

In the above example the options will inform Pax Exam that you wanna test against Felix version 1.0.4, you set a system property foo=bar and that OSGi framework will have to do a boot delegation for sun. and com.sun.{}.
For a detailed list of options you can checkout the CoreOptions class and java doc or checkout the more detailed description.

Provisioning additional bundles

The last step in setting up your unit test is about specifying what bundles should be installed and started alongside your unit test bundle.
This is archived by specifying them as provision options in the configuration options.

import java.net.URL;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.ops4j.pax.exam.CoreOptions.*;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.Configuration;
import org.ops4j.pax.exam.junit.JUnit4TestRunner;

@RunWith( JUnit4TestRunner.class )
public class MyJUnitTest
{

    @Configuration
    public static Option[] configuration()
    {
        return options(
            provision(
                bundle( "http://repository.ops4j.org/maven2/org/ops4j/pax/logging/pax-logging-api/1.3.0/pax-logging-api-1.3.0.jar" ),
                bundle( "http://repository.ops4j.org/maven2/org/ops4j/pax/logging/pax-logging-service/1.3.0/pax-logging-service-1.3.0.jar" ),
                mavenBundle().groupId( "org.ops4j.pax.url" ).artifactId( "pax-url-mvn" ).version( "0.3.2" )
            )
        );
    }

    @Test
    public void testMethod()
        throws Exception
    {
        assertThat( new URL( "mvn:org.ops4j.pax.web/pax-web-service" ).openStream(), is( notNullValue() ) );
    }

}

The above code instructs Pax Exam to provision (install and start) the three bundles. The first two (pax logging) bundles are provision as simple urls. The last (pax maven url handler) bundle is provisioned as a maven bundle, using maven specific concepts. This is just two of the way a provision bundle can be specified (see details) and there are additional ways you can specify if the bundle should be started or if the bundle should be updated (refreshed).

Running the same test against more OSGi frameworks

One of the features Pax Exam provides is to run the same test against more OSGi frameworks or versions of the same framework.

import org.junit.Test;
import org.junit.runner.RunWith;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import static org.ops4j.pax.exam.CoreOptions.*;
import org.ops4j.pax.exam.Inject;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.Configuration;
import org.ops4j.pax.exam.junit.JUnit4TestRunner;

@RunWith( JUnit4TestRunner.class )
public class MyJUnitTest
{

    @Inject
    BundleContext bundleContext;

    @Configuration
    public static Option[] configuration()
    {
        return options(
            frameworks(
                felix(),
                equinox(),
                knopflerfish()
            )
        );
    }

    @Test
    public void testMethod()
        throws Exception
    {
        System.out.println( bundleContext.getProperty( Constants.FRAMEWORK_VENDOR ) );
    }

}

This will make Pax Exam run the testMethod() in Felix, Equinox and Knopflerfish. Pax Exam facilitates running a test against not only different frameworks but also against specific versions of this frameworks, all version of this frameworks or any other combination of it. Check out configuration page for details.