junit-composite-runner

Write JUnit tests with multiple runner classes.

License

License

Categories

Categories

JUnit Unit Testing
GroupId

GroupId

com.flarestarsoftware
ArtifactId

ArtifactId

junit-composite-runner
Last Version

Last Version

0.1.0
Release Date

Release Date

Type

Type

jar
Description

Description

junit-composite-runner
Write JUnit tests with multiple runner classes.
Project URL

Project URL

https://github.com/diosmosis/junit-composite-runner
Source Code Management

Source Code Management

https://github.com/diosmosis/junit-composite-runner

Download junit-composite-runner

How to add to project

<!-- https://jarcasting.com/artifacts/com.flarestarsoftware/junit-composite-runner/ -->
<dependency>
    <groupId>com.flarestarsoftware</groupId>
    <artifactId>junit-composite-runner</artifactId>
    <version>0.1.0</version>
</dependency>
// https://jarcasting.com/artifacts/com.flarestarsoftware/junit-composite-runner/
implementation 'com.flarestarsoftware:junit-composite-runner:0.1.0'
// https://jarcasting.com/artifacts/com.flarestarsoftware/junit-composite-runner/
implementation ("com.flarestarsoftware:junit-composite-runner:0.1.0")
'com.flarestarsoftware:junit-composite-runner:jar:0.1.0'
<dependency org="com.flarestarsoftware" name="junit-composite-runner" rev="0.1.0">
  <artifact name="junit-composite-runner" type="jar" />
</dependency>
@Grapes(
@Grab(group='com.flarestarsoftware', module='junit-composite-runner', version='0.1.0')
)
libraryDependencies += "com.flarestarsoftware" % "junit-composite-runner" % "0.1.0"
[com.flarestarsoftware/junit-composite-runner "0.1.0"]

Dependencies

compile (2)

Group / Artifact Type Version
junit : junit jar 4.12
javassist : javassist jar 3.12.1.GA

Project Modules

There are no modules declared in this project.

junit-composite-runner

A special JUnit Runner class that composites other Runner classes so more than one can be used to run a test.

Caveat

JUnit is not designed for Runner classes to be composited together. In fact, it's not possible out of the box.

This library makes it possible through reflection and bytecode manipulation. Which means it won't work in every scenario.

Composited Runners have to extend ParentRunner and have to follow the contract precisely. Deviations from established behavior could have strange effects.

Usage

To combine multiple Runners, use the RunWith(CompositeRunner.class) annotation and the Runners notation like so:

@RunWith(CompositeRunner.class)
@Runners(value = BlockJUnit4ClassRunner.class, others = {MyTestRunner.class})
public class MyTest {
    // ...
}

The first test Runner class provided to @Runners is considered the test structure provider. This Runner determines what tests are run (eg, by looking for @Test annotations).

The other Runners (specified in the others = annotation property) will only influence how the tests are invoked.

This means that if you use BlockJUnit4ClassRunner as your test structure provider and composite it with a custom runner that looks for test methods a different way (eg, w/ a @MyTest annotation), the tests found by your custom test runner will not be invoked.

How it works

Runner composition is accomplished by carefully chaining runner executions.

A JUnit Runner has three different responsibilities:

  • It determines what tests need to be run
  • It provides logic to execute all tests at once (surrounding the execution w/ logic, like @BeforeClass /@AfterClass methods). This is encapsulated in the ParentRunner.classBlock() method which uses the ParentRunner.childrenInvoker() method.
  • And it provides logic to execute a single test after creating a test instance (surrounding the execution w/ logic, like @Before/@After methods). This is encapsulated in the ParentRunner.runChild() method.

The CompositeRunner uses Javassist to dynamically generate subclasses of each runner used. These subclasses override the ParentRunner.childrenInvoker() and ParentRunner.runChild() methods so that instead of performing the normal behavior, they invoke a method in the next test Runner in the chain.

When the overriden ParentRunner.childrenInvoker() method is called, the subclass will call ParentRunner.classBlock() in the next runner. If there is no next runner, then it calls runChildren() on the first runner in the chain. This means each runner's class level setup/teardown logic will be invoked in turn.

Likewise, the overridden ParentRunner.runChild() method will call ParentRunner.runChild() on the next runner.* If there is no next runner, then we just let ParentRunner.runChild() invoke the test method. This means each runner's setup/teardown logic will be invoked in turn.

* It's actually a bit more complicated than this. Since runChild() will invoke the test we can intercept the execution at the right point and go to the next runner. So instead, we create a dummy FrameworkMethod instance which invokes the next runner's runChild() method, instead of the actual test method.

Versions

Version
0.1.0