Pax Exam - Tutorial 1

Tutorial 1 : Starting from Zero

This tutorial assumes you are using Pax Runner as Test Container and JUnit4 to run your tests.
Use the following dependencies to do so:

 <dependency>
      <groupId>org.ops4j.pax.exam</groupId>
      <artifactId>pax-exam</artifactId>
      <version>${pom.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.ops4j.pax.exam</groupId>
      <artifactId>pax-exam-junit</artifactId>
      <version>${pom.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.ops4j.pax.exam</groupId>
      <artifactId>pax-exam-container-default</artifactId>
      <version>${pom.version}</version>
      <scope>test</scope>
    </dependency>

(among others)

You can find the tutorial project at https://scm.ops4j.org/repos/ops4j/projects/pax/exam/pax-exam-tutorial.

All Test Cases are Standalone Snippets.
Each tutorial builds on knowledge from previous, going just very small steps forward.
This makes it possible to be followed even before reading the Documentation.

Preparation

The tutorial project build with Apache Maven so you will need to have it installed before being able to build and run the tutorial.
Then follow this steps (from a console):

  • Checkout Pax Exam
    svn co https://scm.ops4j.org/repos/ops4j/projects/pax/exam/ exam
    
  • Build Pax Exam
    cd exam
    mvn clean install
    
  • Build and run the tutorial
    cd pax-exam-tutorial
    mvn clean install
    

Note that you can archive similar results by using your preferred IDE.

Tutorial 1 Session 1 : Hello World ( T1S1_HelloWorldTest.java )

This is the very basic. It does not have any custom configuration but just shows
what is needed as a raw minimum.
This is: Setting the @RunWith annotation to JUnit4TestRunner

Thats truly all what it takes to let your test (using junit4 markup like @Test annotation) run inside its own JVM in Apache Felix.

@RunWith( JUnit4TestRunner.class )

Tutorial 1 Session 2 : How to use bundle context ( T1S2_HowToUseBundleContextTest.java )

Here you see how to get a reference of the test bundle's bundleContext.
You do this via annotating a member variable of type BundleContext with the @Inject annotation:

@Inject
BundleContext bundleContext;

Tutorial 1 Session 3 : Hello from Equinox ( T1S3_HelloFromEquinoxTest.java )

Here's how to set a different OSGi Framework Implementation for your test to run in.
This is the first time you'll meet Pax Exam's extensive configuration capability.

Just annotate a method returning Option[] with the @Configuration annotation like so:

@Configuration
public Option[] configure() {
return options(
            equinox()
        );
}

Tutorial 1 Session 4 : More on Configuration ( T1S4_MoreConfigurationTest.java )

Here we go a bit more into the configuration topic showing how to:

  • add a maven bundle dependency
  • add a"normal" dependency ( not bundle ) and let it be converted to a bundle on the fly ( wrapping )
  • using Pax Runner Profiles like "logProfile"
  • setting default log level when using pax logging ( which you do when using logProfile )
 @Configuration
    public static Option[] configure()
    {
        return options(
            // install log service using pax runners profile abstraction (there are more profiles, like DS)
            logProfile(),
            // this is how you set the default log level when using pax logging (logProfile)
            systemProperty( "org.ops4j.pax.logging.DefaultServiceLog.level" ).value( "INFO" ),

            // a maven dependency. This must be a bundle already.
            mavenBundle().groupId( "org.ops4j.pax.url" ).artifactId( "pax-url-mvn" ).version( "0.4.0" ),

            // a maven dependency. OSGi meta data (package exports/imports) are being generated by bnd automatically.
            wrappedBundle(
                mavenBundle().groupId( "org.ops4j.base" ).artifactId( "ops4j-base-util" ).version( "0.5.3" )
            )

        );

You can miss out using the version numbers from your test case by using the depends-maven-plugin from ServiceMix.

Here is an example test case using it and here is the pom.xml.

If you add this to your pom.xml

  <build>
    <plugins>
      <!-- generate dependencies versions -->
      <plugin>
        <groupId>org.apache.servicemix.tooling</groupId>
        <artifactId>depends-maven-plugin</artifactId>
        <executions>
          <execution>
            <id>generate-depends-file</id>
            <goals>
              <goal>generate-depends-file</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

Then you can let the version be deduced for you as follows

mavenBundle().groupId( "org.ops4j.pax.url" ).artifactId( "pax-url-mvn" ).version( asInProject() )

The depends-maven-plugin generates the file target/classes/META-INF/maven/dependencies.properties which Pax Exam can then use to resolve the version dependencies of groupId/artifactIds. This lets you keep all your version information in your pom.xml and avoid having to update all your test cases whenever your pom.xml changes - keeping things DRY.

Tutorial 1 Session 5 : How to debug ( T1S5_HowToDebug.java )

A repeating question is on how to debug my test in pax exam ?
Normally, you would set a breakpoint and let the test run in your debugger.

Because your test will not run inside the vm you start by using your debugger but in a different one (its own jvm, just running a osgi framework) you have to use "remote debugging".

That sounds harder than it is.
Actually, you have to add a vmOption to your configuration.
Those vmOptions are being added to the starting commandling of our osgi-VM 1:1.
So, you can do what you would do with any other container you may have used before (tomcat, or alike):

vmOption( "-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005" ),
timeout( 0 )

The timeout is "disabled" by setting to 0 because Pax Exam would otherwise quickly complain about the remote process which was just started is not "responding".

In this case, you may need a little more time to actually start/configure you remote debugger session.

So here its better to remove this - otherwise very useful and important - behavior.

At this point you are ready to continue with reading the Documentation or start coding