@RunWith JUnit4 with BOTH SpringJUnit4ClassRunner and Parameterized
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:
-
@Autowired
-
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:
-
@RunWith(SpringJUnit4ClassRunner.class)
-
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
-
public class EmailTest {
-
-
@Autowired
-
MyComponent component;
-
//… awesome tests
-
}
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:
-
@Test
-
public void testIsValidEmail() throws Exception {
-
assertTrue("hey, that's an good email!", isValidEmail(validEmail1));
-
//… more tests…
-
assertTrue("hey, that's an good email!", isValidEmail(validEmail2));
-
//…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:
-
@RunWith(Parameterized.class)
-
public class EmailTest {
-
-
String validEmail;
-
-
/** We're testing only good emails, for the sake of simplicity of the example */
-
@Parameters
-
public static Collection<Object[]> data() {
-
return Arrays.asList(new Object[][]{
-
{"a@a.pl" /*more params here*/}, {"exmaple@example.com", /* more params here*/},
-
});
-
}
-
-
public EmailTest(String validEmail /* more params would land here*/) {
-
this.validEmail = validEmail;
-
}
-
-
@Test
-
public void testIsValidEmail() throws Exception {
-
boolean wasOk = StringTools.isEmail(validEmail);
-
assertTrue("This should have been ok", wasOk);
-
}
-
//… 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:
-
@RunWith(Parameterized.class)
-
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
-
public class EmailTest {
-
-
private TestContextManager testContextManager;
-
-
String validEmail;
-
-
@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);
-
}
-
-
@Parameters
-
public static Collection<object []> data() {
-
return Arrays.asList(new Object[][]{
-
{"a@a.pl"},{"exmaple@example.com"},
-
});
-
}
-
-
public EmailTest(String validEmail) {
-
this.validEmail = validEmail;
-
}
-
-
@Test
-
public void testIsValidEmail() throws Exception {
-
boolean email = StringTools.isEmail(validEmail);
-
assertTrue("should be OK", wasOk);
-
}
-
//… 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 ;-)







November 24th, 2010 at 15:20
Thank you very much! Exactly what I was looking for…
November 24th, 2010 at 23:38
You’re very welcome, fellow FSF supporter! :-)
March 16th, 2011 at 23:42
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
March 17th, 2011 at 01:10
Sure. Just as the Exception states: “No public static parameters method on [...]“. Make sure your Parameters method has exactly this signature:
@Parameterspublic static Collection
March 24th, 2011 at 21:21
Thanks man, great article, you save my life ;)
cheers!
May 11th, 2011 at 11:19
THANKS very much
fantastic solution
thnx
June 24th, 2011 at 07:39
Thanks. Good solution !
July 20th, 2011 at 10:44
This was VERY helpful, exactly what I was looking for.
THANKS a lot.
August 12th, 2011 at 11:27
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
August 14th, 2011 at 16:50
@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.
February 8th, 2012 at 11:46
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?
March 13th, 2013 at 08:06
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