How can I use the Parameterized JUnit test runner with a field that's injected using Spring?

Viewed 21113

I'm using Spring to inject the path to a directory into my unit tests. Inside this directory are a number of files that should be used to generate test data for parameterized test cases using the Parameterized test runner. Unfortunately, the test runner requires that the method that provides the parameters be static. This doesn't work for my situation because the directory can only be injected into a non-static field. Any ideas how I can get around this?

7 Answers

Here is a first solution without JUnit 4.12 parameterized factory, below an improved solution with it.

Static context without transactional support

Let Spring do all configuration parsing and autowiring with TestContextManager class.

The trick is to use a fake test instance to get autowired fields and pass them to the parameterized test which will effectively run.

But keep in mind prepareTestInstance() do the autowiring but doesn't manage test transaction and other nice stuffs handled by beforeTestMethod() and afterTestMethod().

@RunWith(Parameterized.class)
@ContextConfiguration(locations = {"/test-context.xml", "/mvc-context.xml"})
@WebAppConfiguration
@ActiveProfiles("test-profile")
public class MyTest {

  @Parameters
  public static Collection<Object[]> params() throws Exception {
    final MyTest fakeInstance = new MyTest();
    final TestContextManager contextManager = new TestContextManager(MyTest.class);
    contextManager.prepareTestInstance(fakeInstance);
    final WebApplicationContext context = fakeInstance.context;

    // Do what you need with Spring context, you can even access web resources
    final Resource[] files = context.getResources("path/files");
    final List<Object[]> params = new ArrayList<>();
    for (Resource file : files) {
      params.add(new Object[] {file, context});
    }
    return params;
  }

  @Parameter
  public Resource file;
  @Autowired
  @Parameter(1)
  public WebApplicationContext context;
}

However a drawback appear if you have a lot of autowired fields because you have to manually pass them to the array parameters.

Parameterized factory with full Spring support

JUnit 4.12 introduce ParametersRunnerFactory which allow to combine parameterized test and Spring injection.

public class SpringParametersRunnerFactory implements ParametersRunnerFactory {
@Override
  public Runner createRunnerForTestWithParameters(TestWithParameters test) throws InitializationError {
    final BlockJUnit4ClassRunnerWithParameters runnerWithParameters = new BlockJUnit4ClassRunnerWithParameters(test);
    return new SpringJUnit4ClassRunner(test.getTestClass().getJavaClass()) {
      @Override
      protected Object createTest() throws Exception {
        final Object testInstance = runnerWithParameters.createTest();
        getTestContextManager().prepareTestInstance(testInstance);
        return testInstance;
      }
    };
  }
}

The factory can be added to previous test class to give full Spring support like test transaction, reinit dirty context and servlet test. And of course there no more need to pass autowired fields from fake test instance to parameterized test.

@UseParametersRunnerFactory(SpringParametersRunnerFactory.class)
@RunWith(Parameterized.class)
@ContextConfiguration(locations = {"/test-context.xml", "/mvc-context.xml"})
@WebAppConfiguration
@Transactional
@TransactionConfiguration
public class MyTransactionalTest {

  @Parameters
  public static Collection<Object[]> params() throws Exception {
    final MyTransactionalTest fakeInstance = new MyTransactionalTest();
    final TestContextManager contextManager = new TestContextManager(MyTransactionalTest.class);
    contextManager.prepareTestInstance(fakeInstance);
    final WebApplicationContext context = fakeInstance.context;

    // Do what you need with Spring context, you can even access web resources
    final Resource[] files = context.getResources("path/files");
    final List<Object[]> params = new ArrayList<>();
    for (Resource file : files) {
      params.add(new Object[] {file});
    }
    return params;
  }

  @Parameter
  public Resource file;

  @Autowired
  private WebApplicationContext context;
}
Related