Skip to content

Tips and Tricks

Andreas Schmid edited this page Oct 28, 2018 · 15 revisions

JUnit4 DataProvider for Dependency Injection Frameworks

To use the junit-dataprovider's @DataProvider in integration tests you need to extend the runners that manage the containers.

JUnit4 DataProvider for CDI

Simply copy the source code of DataProviderRunner.java into your project and rename it to something like CdiDataProviderRunner.java and then replace BlockJUnit4ClassRunner with CdiRunner, i.e., write

public class CdiDataProviderRunner extends CdiRunner

This was tested and works for the basic use cases with junit-dataprovider v1.10.0 and cdi-unit v3.1.3. You are welcome to report working/non-working combinations.

JUnit4 DataProvider for Spring Testing

Since Spring version 4.2

Spring introduced two JUnit rules that supports features of the Spring TestContext Framework in standard JUnit4 tests by means of the TestContextManager and associated support classes and annotations.

Before Spring version 4.2

To use the junit-dataprovider together with older Spring integration testing, i.e. tests which have to run with the SpringJUnit4ClassRunner, you need to create a new JUnit runner similar to

import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

public class DataProviderRunnerWithSpring extends SpringJUnit4ClassRunner {
    /* ... */
}

Note: This requires JUnit 4.12, see SpringJUnit4ClassRunner.

Since JUnit dataprovider v1.10.4

This is all what is required, see example integration test in package com.tngtech.test.java.junit.dataprovider.spring.

Before JUnit dataprovider v1.10.4

The body of this class is identical with DataProviderRunner.java but needs a small hack to work for version v1.10.3 and smaller. Without any modification a NullPointerException is thrown in

  void invokeBeforeClass() {
    Statement statement = new Statement() {
      @Override
      public void evaluate() {
        // nothing to do here
      }
    };
    statement = super.withBeforeClasses(statement);
    try {
      statement.evaluate();
    } catch (Throwable e) {
      failure = e;
    }
  }

The simplest solution is to comment out statement.evaluate();, which obviously breaks the @BeforeClass annotation. Alternatively, replacing the function above by the following two functions also fixes the issue without breaking @BeforeClass.

  void invokeBeforeClass() {
    Statement statement = new Statement() {
      @Override
      public void evaluate() {
        // nothing to do here
      }
    };
    Statement runBeforeTestClassCallbacks = super.withBeforeClasses(statement);

    // ATTENTION: This hack breaks "org.springframework.test.context.support.AbstractTestExecutionListener.beforeTestClass(TestContext)" 
    // as it will never be called
    Statement next = getNextFromRunBeforeTestClassCallbacks(runBeforeTestClassCallbacks);
    try {
      next.evaluate();
    } catch (Throwable e) {
      failure = e;
    }
  }

  private Statement getNextFromRunBeforeTestClassCallbacks(Statement junitBeforeClasses) {
    try {
      Field nextField = junitBeforeClasses.getClass().getDeclaredField("next");
      nextField.setAccessible(true);
      return ((Statement) nextField.get(junitBeforeClasses));
    } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
      throw new RuntimeException("Could not get 'next'-Field from 'runBeforeTestClassCallbacks' statement.", e);
    }
  }

This workaround results in org.springframework.test.context.support.AbstractTestExecutionListener.beforeTestClass(TestContext) to never be called. As the default implementation of this method does not contain any operations, this should be acceptable in most cases.

Note: Some people claimed that above guide is not working for them, see e.g. https://github.com/TNG/junit-dataprovider/issues/79#issuecomment-433690927 but claim https://www.lenar.io/junit-dataprovider-with-spring-example/ to be working.