Skip to content
Jaime Schettini Filho edited this page Feb 2, 2016 · 18 revisions

Anyone familiar with Ruby Specs knows that using hierarchical contexts to structure your unit tests helps to keep your tests clean and readable, while it also reduces the number of boilerplate setup code tremendously.

After watching episode 20 of Uncle Bob’s CleanCoders screencast, I found myself disappointed about the missing context ability of JUnit's default test runner. Also Uncle Bob came up with a workaround of creating contexts by using inner static classes and inheritance (this is all the experimental Enclosed runner supports). I felt like this was wrong and also, that it should be possible to come up with a test runner that supports something like the Ruby Specs Contexts.

The idea was born and I started to work on a hierarchical context runner for JUnit, supporting a structure like the following:

@RunWith(HierarchicalContextRunner.class)
public class MainTest {
  // ... some constants and helpers ...

  @BeforeClass
  public static void runsOnlyOnce_Before_AllTestsInMainTest() throws Exception {...}

  @AfterClass
  public static void runsOnlyOnce_After_AllTestsInMainTest() throws Exception {...}

  @Test
  public void simpleTestInTheMainContext() throws Exception {...}

  @Test
  public void anotherTestInTheMainContext() throws Exception {...}

  // ... more tests in the main context ...

  public class SubContext {
    @Before
    public void runs_Before_EachTestWithinSubContextAndAllContainingContexts() {...}

    @After
    public void runs_After_EachTestWithinSubContextAndAllContainingContexts() {...}

    @Test
    public void simpleTestInTheSubContext() throws Exception {...}

    // ... more tests in the sub context ...

    }

    public class SubSubContext1 {
      @Before
      public void runs_Before_EachTestWithinSubSubContext1AndAllContainingContexts() {...}

      @After
      public void runs_After_EachTestWithinSubSubContext1AndAllContainingContexts() {...}

      @Test
      public void simpleTestInTheSubSubContext1() throws Exception {...}

      // ... more tests in the sub sub context 1 ...
    }

    public class SubSubContext2 {
      @Before
      public void runs_Before_EachTestWithinSubSubContext2AndAllContainingContexts() {...}

      @After
      public void runs_After_EachTestWithinSubSubContext2AndAllContainingContexts() {...}

      @Test
      public void simpleTestInTheSubSubContext2() throws Exception {...}

      // ... more tests in the sub sub context 2 ...
    }
}

In this example, one can see that tests with equal preconditions can be grouped into contexts. It doesn't matter how many contexts you have and it doesn't matter how deep your context hierarchy grows. The HierarchicalContextRunner supports as many contexts as you are required to create to structure your tests well and nicely. (More examples can be found within the samples folder)

Instead of using inner static classes, the hierarchical context runner supports member classes to create a context. This has several advantages over the use of static classes:

  • No inheritance!
  • Well structured tests
  • The context hierarchy is represented within the outline of your IDE
  • The tests within an inner context have access to all members of a higher level context.
  • Static inner classes are not scanned for tests (according to the BlockJUnit4ClassRunner) and can be used to create stubs and helpers

The tricky parts were to support the creation of the test instance and to execute all @Before, @After and @Rule statements along the context hierarchy for each test.

The test instance needs to be built top-down, i.e. create the top level instance, which is required to create the first level instance, which is required to create the second level instance, etc., until the actual test instance can be created. In order to execute all @Before, @After and @Rule statements defined along the hierarchy of a context another extension needs to be placed to scan all classes along the context hierarchy and gather the statements accordingly.

With these changes in place the runner works great. As it was built upon the BlockJUnit4ClassRunner it also supports all features that are supported by this runner. Therefore, no need to hesitate. Check it out today and give it a try. Feedback is very welcomed and appreciated. :)

To use the runner, simply add the following maven dependency to your project or click one of the downloads below:

Maven Dependency

<dependency>
  <groupId>de.bechte.junit</groupId>
  <artifactId>junit-hierarchicalcontextrunner</artifactId>
  <version>4.12.1</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>de.bechte.junit</groupId>
  <artifactId>junit-hierarchicalcontextrunner</artifactId>
  <version>4.11.3</version>
  <scope>test</scope>
</dependency>

Downloads

The first two version numbers should match your JUnit version, e.g. 4.12 here. The last version number is a patch level for the HierarchicalContextRunner. You should always use the highest patch level available for your JUnit version.

Clone this wiki locally