67

The following code is invalid due to duplicate @RunWith annotation:

@RunWith(SpringJUnit4ClassRunner.class)
@RunWith(Parameterized.class)
@SpringApplicationConfiguration(classes = {ApplicationConfigTest.class})
public class ServiceTest {
}

But how can I use these two annotations in conjunction?

membersound
  • 81,582
  • 193
  • 585
  • 1,120
  • 1
    Here is another - and up to date - solution http://blog.codeleak.pl/2015/08/parameterized-integration-tests-with.html – Martin Podval Nov 09 '15 at 15:10

5 Answers5

86

You can use SpringClassRule and SpringMethodRule - supplied with Spring

import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.springframework.test.context.junit4.rules.SpringClassRule;
import org.springframework.test.context.junit4.rules.SpringMethodRule;

@RunWith(Parameterized.class)
@ContextConfiguration(...)
public class MyTest {

    @ClassRule
    public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule();

    @Rule
    public final SpringMethodRule springMethodRule = new SpringMethodRule();

    ...
keyoxy
  • 4,423
  • 2
  • 21
  • 18
38

There are at least 2 options to do that:

  1. Following http://www.blog.project13.pl/index.php/coding/1077/runwith-junit4-with-both-springjunit4classrunner-and-parameterized/

    Your test needs to look something like this:

     @RunWith(Parameterized.class)
     @ContextConfiguration(classes = {ApplicationConfigTest.class})
     public class ServiceTest {
    
         private TestContextManager testContextManager;
    
         @Before
         public void setUpContext() throws Exception {
             //this is where the magic happens, we actually do "by hand" what the spring runner would do for us,
            // read the JavaDoc for the class bellow to know exactly what it does, the method names are quite accurate though
           this.testContextManager = new TestContextManager(getClass());
           this.testContextManager.prepareTestInstance(this);
         }
         ...
     }
    
  2. There is a github project https://github.com/mmichaelis/spring-aware-rule, which builds on previous blog, but adds support in a generalized way

    @SuppressWarnings("InstanceMethodNamingConvention")
    @ContextConfiguration(classes = {ServiceTest.class})
    public class SpringAwareTest {
    
        @ClassRule
        public static final SpringAware SPRING_AWARE = SpringAware.forClass(SpringAwareTest.class);
    
        @Rule
        public TestRule springAwareMethod = SPRING_AWARE.forInstance(this);
    
        @Rule
        public TestName testName = new TestName();
    
        ...
    }
    

So you can have a basic class implementing one of the approaches, and all tests inheriting from it.

mavarazy
  • 7,562
  • 1
  • 34
  • 60
  • Great, and with the first way you can then just use `@Autowired` on a component and it will be injected correctly. Also, I could use `@SpringApplicationConfiguration` instead of `@ContextConfiguration` and it worked fine, not sure what the difference is... – nyg Apr 08 '16 at 11:59
  • 2
    Your first link seems to be dead :-( – Francois Bourgeois Aug 06 '18 at 12:58
  • @mavarazy I've tried the first solution: Works! Thank you so much. – Rami Yampolsky Jan 24 '19 at 20:34
1

There is another solution with JUnit 4.12 without the need of Spring 4.2+.

JUnit 4.12 introduces 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 test class to give full Spring support like test transaction, reinit dirty context and servlet test.

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

  @Autowired
  private WebApplicationContext context;

  ...
}

If you need Spring context inside @Parameters static method to provide parameters to test instances, please see my answer here How can I use the Parameterized JUnit test runner with a field that's injected using Spring?.

Arnor
  • 106
  • 5
0

Handle application context by yourself

What worked for me was having a @RunWith(Parameterized.class) test class that managed the application context "by hand".

To do that I created an application context with the same string collection that would be in the @ContextConfiguration. So instead of having

@ContextConfiguration(locations = { "classpath:spring-config-file1.xml",
    "classpath:spring-config-file2.xml" })

I had

ApplicationContext ctx = new ClassPathXmlApplicationContext(new String[] {
            "classpath:spring-config-file1.xml", "classpath:spring-config-file2.xml"  });

And for each @Autowired I needed I fetched it by hand from the created context:

SomeClass someBean = ctx.getBean("someClassAutowiredBean", SomeClass.class);

Do not forget to close the context at the end:

((ClassPathXmlApplicationContext) ctx).close();
manuelvigarcia
  • 1,696
  • 1
  • 22
  • 32
0

This question was answered long ago but I want to post my alternative solution:

I kept the spring configuration as it is:

@RunWith(SpringRunner.class) @SpringBootTest(classes = XXXXWebApplication.class)

And instead of using annotation @Parameters I just put the variables in a global array and iterate over it when running the test to execute each single scenario.

The simplest.

Eduardo
  • 2,070
  • 21
  • 26