Passing parameters to a JUnit runner

This article explains how to pass parameters to a JUnit runner (through annotated test classes). Although I used an extension of Pax-Exam’s JUnit runner, this can be applied to any JUnit runner.

In few words…

I have a custom JUnit runner that had to check assertions on the test environment before running tests. By assertions, I mean “is Docker installed on the machine?”, “is RabbitMQ installed?”, “is RabbitMQ installed with credentials user/pwd?”, etc.

These tests are not unit tests, but integration tests.
And they rely on JUnit. Or more exactly, they rely on Pax-Exam, which itself rely on JUnit. Pax-Exam is a tool that allows to run tests within an OSGi environment (Apache Karaf in my case). Tests that use a probe work as follows: first, the OSGi container is deployed and/or started. Then, a probe is injected in this container (as an OSGi bundle) and it is this probe that runs the tests.

Usually, with JUnit, one would use the Assume functions to verify a test can run. If a condition verified by Assume is not satisfied, then the test is skipped. With Pax-Exam, this would not be very efficient, because before running the test, and thus detecting an assumption is wrong, the OSGi container would have been set up. And this takes time. What I wanted was to verify assertions on the environment BEFORE the container was set up. So, I sub-classed Pax’s runner, which itself extends JUnit’s runner.

And since not all my tests have the same requirements, I had to find a way to pass parameters to my JUnit runner. Notice this is not parameterized tests. Parameterized tests imply passing parameters to a test class. What I wanted was to pass parameters to the runner (the class that runs the tests) through the test class.

The solution

Well, nothing original here. I created a custom Java annotation.

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface RoboconfITConfiguration {

	/**
	 * @return true if the test requires RabbitMQ running with default credentials
	 */
	boolean withRabbitMq() default true;

	/**
	 * @return true if the test requires RabbitMQ running with "advanced" credentials
	 */
	boolean withComplexRabbitMq() default false;

	/**
	 * @return true if the test required Docker to be installed on the local machine
	 */
	boolean withDocker() default false;
}

It is used by my test classes to specify assertions on the environment.

@RunWith( RoboconfPaxRunner.class )
@RoboconfITConfiguration( withDocker = true, withComplexRabbitMq = true )
@ExamReactorStrategy( PerMethod.class )
public class LocalDockerWithAgentChecksTest {

When my runner loads the test class, it verifies if it is annotated. And if so, it verifies the assumptions on the environment. If one fails, then the test is skipped. This has the advantage of being able to compile and run as much tests as possible, even when the compile environment is not complete.

Here is the global look of my runner.
It only adds an overlay on the default Pax-Exam runner.

public class RoboconfPaxRunner extends PaxExam {

	private final Class<?> testClass;


	/**
	 * Constructor.
	 * @param klass
	 * @throws InitializationError
	 */
	public RoboconfPaxRunner( Class<?> klass ) throws InitializationError {
		super( klass );
		this.testClass = klass;
	}


	@Override
	public void run( RunNotifier notifier ) {

		boolean runTheTest = true;
		if( this.testClass.isAnnotationPresent( RoboconfITConfiguration.class )) {
			RoboconfITConfiguration annotation = this.testClass.getAnnotation( RoboconfITConfiguration.class );

			// Default RMQ settings
			if( annotation.withRabbitMq()
					&& ! RabbitMqTestUtils.checkRabbitMqIsRunning()) {
				Description description = Description.createSuiteDescription( this.testClass );
				notifier.fireTestAssumptionFailed( new Failure( description, new Exception( "RabbitMQ is not running." )));
				runTheTest = false;
			}

			// Advanced RMQ settings
			else { /* etc. */ }
		}

		// No annotation? Consider RMQ must be installed by default.
		else if( ! RabbitMqTestUtils.checkRabbitMqIsRunning()) {
			Description description = Description.createSuiteDescription( this.testClass );
			notifier.fireTestAssumptionFailed( new Failure( description, new Exception( "RabbitMQ is not running." )));
			runTheTest = false;
		}

		// If everything is good, run the test
		if( runTheTest )
			super.run( notifier );
	}
}

About this entry