@RunWith JUnit4 with BOTH SpringJUnit4ClassRunner and Parameterized

Posted by Konrad 'ktoso' Malawski on 13/11/2010 – 01:05;

Ok, now for a quick trick before I get into writing more about git and our last javacamp (movies are still being converted, sorry for the long wait).

If you code in Java, you most probably use (you really should use) some dependecy injection mechanisms. They’re really great and take care about all the setup that need’s to be done before you can move on to your coding. The same thigh applies to testing, you’d rather write:

//yay
  1.     @Autowired
  2.     MyComponent component;

Than use the new operator, or worse, perform some super weird setup to build this object. If you use spring, you’d write an test like this to make it support Spring’s DI:

//an JUnit test with spring DI
  1. @RunWith(SpringJUnit4ClassRunner.class)
  2. @ContextConfiguration(locations = {"classpath:applicationContext.xml"})
  3. public class EmailTest {
  4.    
  5.     @Autowired
  6.     MyComponent component;
  7.     //… awesome tests
  8. }

That’s all you need to make your tests properly autowire all components they’re using. So far so good – probably nothing new here, and nothing complicated. So let’s move on to some “super big data set” that needs to be tested, over the same flow over and over again. We’ll use an super conplicated example to showcase what I mean:

//inside an test class
  1. @Test
  2. public void testIsValidEmail() throws Exception {
  3.     assertTrue("hey, that's an good email!", isValidEmail(validEmail1));
  4.     //… more tests…
  5.     assertTrue("hey, that's an good email!", isValidEmail(validEmail2));
  6.     //…over and over again…

Ok, that’s obviously stupid… :-) So, how do we deal with repetitive tests, that need some bigger dataset than just this email example (given here because it’s short and good enough for our example test)…? We’d use @RunWith(Parameterized.class), the class would then look something like this:

//my test with only params
  1. @RunWith(Parameterized.class)
  2.     public class EmailTest {
  3.    
  4.     String validEmail;
  5.  
  6.     /** We're testing only good emails, for the sake of simplicity of the example */
  7.     @Parameters
  8.     public static Collection<Object[]> data() {
  9.         return Arrays.asList(new Object[][]{
  10.             {"a@a.pl" /*more params here*/}, {"exmaple@example.com", /* more params here*/},
  11.         });
  12.     }
  13.  
  14.     public EmailTest(String validEmail /* more params would land here*/) {
  15.         this.validEmail = validEmail;
  16.     }
  17.  
  18.     @Test
  19.     public void testIsValidEmail() throws Exception {
  20.         boolean wasOk = StringTools.isEmail(validEmail);
  21.         assertTrue("This should have been ok", wasOk);
  22.     }
  23.     //… more stuff

Ok, that’s a nice way to make your code in the tests smaller yet have an nice overview through all of your tested data. Each array will be inserted in the constructor, and then ran as an seperate test – with each dataset. Nice, isn’t it?

Ok, but what if we want to have both Spring’s DI and an @Parameters “test data provider”…? Can you spot the problem? Yup, JUnit4 can only have ONE @RunWith annotation, and it doesn’t accept multiple runners. Which totaly makes sense when you think about it, but then there is our special case of DI, which really doesn’t change the way an test wotks, it just makes setup easier… All that said, here’s how to use both spring and parameters in your Junit4 tests:

//mytest with DI and params
  1.     @RunWith(Parameterized.class)
  2.     @ContextConfiguration(locations = {"classpath:applicationContext.xml"})
  3.     public class EmailTest {
  4.  
  5.     private TestContextManager testContextManager;
  6.  
  7.     String validEmail;
  8.  
  9.     @Before
  10.     public void setUpContext() throws Exception {
  11.         //this is where the magic happens, we actually do "by hand" what the spring runner would do for us,
  12.         // read the JavaDoc for the class bellow to know exactly what it does, the method names are quite accurate though
  13.         this.testContextManager = new TestContextManager(getClass());
  14.         this.testContextManager.prepareTestInstance(this);
  15.     }
  16.  
  17.     @Parameters
  18.     public static Collection<object []> data() {
  19.         return Arrays.asList(new Object[][]{
  20.             {"a@a.pl"},{"exmaple@example.com"},
  21.         });
  22.     }
  23.  
  24.     public EmailTest(String validEmail) {
  25.         this.validEmail = validEmail;
  26.     }
  27.  
  28.     @Test
  29.     public void testIsValidEmail() throws Exception {
  30.         boolean email = StringTools.isEmail(validEmail);
  31.         assertTrue("should be OK", wasOk);
  32.     }
  33.     //… more stuff…</object>

That’s it :-) We do what the spring Runner would do for us, instanciate an application context and prepare it – that’s when the DI happens. Note that we cant use another runner annotation, but we can as usual specify where to find the appContext.xml’s.

That’s all, hope this will prove useful to some of you :-) Keep your tests clean!

PS: Warning, most of this code was written in wordpress, it may contain minor spelling errors ;-)

Tags: , , , , , , ,

This post is under “coding, english, java” and has 14 respond so far.

14 Responds so far- Add one»

  1. 1. mercibe Said:

    Thank you very much! Exactly what I was looking for…

  2. 2. Ktoso Said:

    You’re very welcome, fellow FSF supporter! :-)

  3. 3. Alpa Said:

    Hi

    I tried to copy ur code as a tutorial and run the test using parametrrosed.runner.

    It builds but when I try to run the test it fails with the following trace. Do you have any idea about this

    java.lang.Exception: No public static parameters method on class com.expedia.cc.finance.pointofsaleservice.test.functional.POSParameterisedTest
    at org.junit.runners.Parameterized.getParametersMethod(Parameterized.java:158)
    at org.junit.runners.Parameterized.getParametersList(Parameterized.java:144)
    at org.junit.runners.Parameterized.(Parameterized.java:130)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
    at java.lang.reflect.Constructor.newInstance(Unknown Source)
    at org.junit.internal.builders.AnnotatedBuilder.buildRunner(AnnotatedBuilder.java:31)
    at org.junit.internal.builders.AnnotatedBuilder.runnerForClass(AnnotatedBuilder.java:24)
    at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:57)
    at org.junit.internal.builders.AllDefaultPossibilitiesBuilder.runnerForClass(AllDefaultPossibilitiesBuilder.java:29)
    at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:57)
    at org.junit.internal.requests.ClassRequest.getRunner(ClassRequest.java:24)
    at org.junit.internal.requests.FilterRequest.getRunner(FilterRequest.java:33)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.(JUnit4TestReference.java:32)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestMethodReference.(JUnit4TestMethodReference.java:25)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestLoader.createTest(JUnit4TestLoader.java:42)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestLoader.loadTests(JUnit4TestLoader.java:31)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:452)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

    Thanks in advance for your help.
    Alpa

  4. 4. Ktoso Said:

    Sure. Just as the Exception states: “No public static parameters method on [...]“. Make sure your Parameters method has exactly this signature:
    @Parameters
    public static Collection data() { /*method body*/ }

  5. 5. mercenary Said:

    Thanks man, great article, you save my life ;)

    cheers!

  6. 6. Anas Samara Said:

    THANKS very much
    fantastic solution
    thnx

  7. 7. Amit Said:

    Thanks. Good solution !

  8. 8. Patrick Dreyer Said:

    This was VERY helpful, exactly what I was looking for.
    THANKS a lot.

  9. 9. Sandeep Said:

    but what if we are using PrivateAccessor to call private method from some other class.
    we need to pass the reference of the class and it is giving error for that

  10. 10. Ktoso Said:

    @Sandeep: Are you trying to test private methods? Don’t do that, instead make them package accessible or extract them to some small class which you could easily test.

  11. 11. Dumanshu Said:

    hey.. similar kind of code gives error for my case. It states that the spring was not able to instantiate the beans because the constructor was not provided along with. The constructor I have provided is for the parameters purpose. If I overload the constructor with no parameters, It gives the error stating that the test class must have only one constructor!.. any ideas?

  12. 12. Viral Said:

    Thanks, It worked like a charm.

    With Junit 4.11, user can provide custom name to a test.

    @Parameters(name=”{index}: Request_{0}”)
    public static Collection data() {

    }
    Note:
    index represents a counter on data.
    {0} represents first argument of an object

  13. 13. RobertZ Said:

    Hi,
    I need to get a iBatis db connection in my @parameters body. However the @before is called AFTER the @Parameters so I don’t get a chance to initialize my iBatis config.

    In your example which one is called first @Before or @Parameters

    Thanks,
    Robert

Post a reply