1 package junitparams; 2 3 import java.util.List; 4 5 import junitparams.internal.MethodBlockSupplier; 6 import org.junit.runner.Description; 7 import org.junit.runner.notification.RunNotifier; 8 import org.junit.runners.BlockJUnit4ClassRunner; 9 import org.junit.runners.model.FrameworkMethod; 10 import org.junit.runners.model.InitializationError; 11 import org.junit.runners.model.Statement; 12 13 import junitparams.internal.DescribableFrameworkMethod; 14 import junitparams.internal.InstanceFrameworkMethod; 15 import junitparams.internal.InvokableFrameworkMethod; 16 import junitparams.internal.NonParameterisedFrameworkMethod; 17 import junitparams.internal.ParameterisedFrameworkMethod; 18 import junitparams.internal.TestMethod; 19 20 /** 21 * <h1>JUnitParams</h1><br> 22 * <p> 23 * This is a JUnit runner for parameterised tests that don't suck. Annotate your test class with 24 * <code>@RunWith(JUnitParamsRunner.class)</code> and place 25 * <code>@Parameters</code> annotation on each test method which requires 26 * parameters. Nothing more needed - no special structure, no dirty tricks. 27 * </p> 28 * <br> 29 * <h2>Contents</h2> <b> <a href="#p1">1. Parameterising tests</a><br> 30 * <a href="#a">a. Parameterising tests via values 31 * in annotation</a><br> 32 * <a href="#b">b. Parameterising tests via a 33 * method that returns parameter values</a><br> 34 * <a href="#c">c. Parameterising tests via 35 * external classes</a><br> 36 * <a href="#d">d. Loading parameters from files</a><br> 37 * <a href="#d">e. Converting parameter values</a><br> 38 * <a href="#p2">2. Usage with Spring</a><br> 39 * <a href="#p3">3. Other options</a><br> 40 * </b><br> 41 * <h3 id="p1">1. Parameterising tests</h3> Parameterised tests are a great way 42 * to limit the amount of test code when you need to test the same code under 43 * different conditions. Ever tried to do it with standard JUnit tools like 44 * Parameterized runner or Theories? I always thought they're so awkward to use, 45 * that I've written this library to help all those out there who'd like to have 46 * a handy tool. 47 * 48 * So here we go. There are a few different ways to use JUnitParams, I will try 49 * to show you all of them here. 50 * 51 * <h4 id="a">a. Parameterising tests via values in annotation</h4> 52 * <p> 53 * You can parameterise your test with values defined in annotations. Just pass 54 * sets of test method argument values as an array of Strings, where each string 55 * contains the argument values separated by a comma or a pipe "|". 56 * 57 * <pre> 58 * @Test 59 * @Parameters({ "20, Tarzan", "0, Jane" }) 60 * public void cartoonCharacters(int yearsInJungle, String person) { 61 * ... 62 * } 63 * </pre> 64 * 65 * Sometimes you may be interested in passing enum values as parameters, then 66 * you can just write them as Strings like this: 67 * 68 * <pre> 69 * @Test 70 * @Parameters({ "FROM_JUNGLE", "FROM_CITY" }) 71 * public void passEnumAsParam(PersonType person) { 72 * } 73 * </pre> 74 * 75 * <h4 id="b">b. Parameterising tests via a method that returns parameter values 76 * </h4> 77 * <p> 78 * Obviously passing parameters as strings is handy only for trivial situations, 79 * that's why for normal cases you have a method that gives you a collection of 80 * parameters: 81 * 82 * <pre> 83 * @Test 84 * @Parameters(method = "cartoonCharacters") 85 * public void cartoonCharacters(int yearsInJungle, String person) { 86 * ... 87 * } 88 * private Object[] cartoonCharacters() { 89 * return $( 90 * $(0, "Tarzan"), 91 * $(20, "Jane") 92 * ); 93 * } 94 * </pre> 95 * 96 * Where <code>$(...)</code> is a static method defined in 97 * <code>JUnitParamsRunner</code> class, which returns its parameters as a 98 * <code>Object[]</code> array. Just a shortcut, so that you don't need to write the ugly <code>new Object[] {}</code> kind of stuff. 99 * 100 * <p> 101 * <code>method</code> can take more than one method name - you can pass as many 102 * of them as you want, separated by commas. This enables you to divide your 103 * test cases e.g. into categories. 104 * <pre> 105 * @Test 106 * @Parameters(method = "menCharactes, womenCharacters") 107 * public void cartoonCharacters(int yearsInJungle, String person) { 108 * ... 109 * } 110 * private Object[] menCharacters() { 111 * return $( 112 * $(20, "Tarzan"), 113 * $(2, "Chip"), 114 * $(2, "Dale") 115 * ); 116 * } 117 * private Object[] womenCharacters() { 118 * return $( 119 * $(0, "Jane"), 120 * $(18, "Pocahontas") 121 * ); 122 * } 123 * </pre> 124 * <p> 125 * The <code>method</code> argument of a <code>@Parameters</code> annotation can 126 * be ommited if the method that provides parameters has a the same name as the 127 * test, but prefixed by <code>parametersFor</code>. So our example would look 128 * like this: 129 * 130 * <pre> 131 * @Test 132 * @Parameters 133 * public void cartoonCharacters(int yearsInJungle, String person) { 134 * ... 135 * } 136 * private Object[] parametersForCartoonCharacters() { 137 * return $( 138 * $(0, "Tarzan"), 139 * $(20, "Jane") 140 * ); 141 * } 142 * </pre> 143 * 144 * <p> 145 * If you don't like returning untyped values and arrays, you can equally well 146 * return any Iterable of concrete objects: 147 * 148 * <pre> 149 * @Test 150 * @Parameters 151 * public void cartoonCharacters(Person character) { 152 * ... 153 * } 154 * private List<Person> parametersForCartoonCharacters() { 155 * return Arrays.asList( 156 * new Person(0, "Tarzan"), 157 * new Person(20, "Jane") 158 * ); 159 * } 160 * </pre> 161 * 162 * If we had more than just two Person's to make, we would get redundant, 163 * so JUnitParams gives you a simplified way of creating objects to be passed as 164 * params. You can omit the creation of the objects and just return their constructor 165 * argument values like this: 166 * 167 * <pre> 168 * @Test 169 * @Parameters 170 * public void cartoonCharacters(Person character) { 171 * ... 172 * } 173 * private List<?> parametersForCartoonCharacters() { 174 * return Arrays.asList( 175 * $(0, "Tarzan"), 176 * $(20, "Jane") 177 * ); 178 * } 179 * </pre> 180 * And JUnitParams will invoke the appropriate constructor (<code>new Person(int age, String name)</code> in this case.) 181 * <b>If you want to use it, watch out! Automatic refactoring of constructor 182 * arguments won't be working here!</b> 183 * 184 * <p> 185 * You can also define methods that provide parameters in subclasses and use 186 * them in test methods defined in superclasses, as well as redefine data 187 * providing methods in subclasses to be used by test method defined in a 188 * superclass. That you can doesn't mean you should. Inheritance in tests is 189 * usually a code smell (readability hurts), so make sure you know what you're 190 * doing. 191 * 192 * <h4 id="c">c. Parameterising tests via external classes</h4> 193 * <p> 194 * For more complex cases you may want to externalise the method that provides 195 * parameters or use more than one method to provide parameters to a single test 196 * method. You can easily do that like this: 197 * 198 * <pre> 199 * @Test 200 * @Parameters(source = CartoonCharactersProvider.class) 201 * public void testReadyToLiveInJungle(int yearsInJungle, String person) { 202 * ... 203 * } 204 * ... 205 * class CartoonCharactersProvider { 206 * public static Object[] provideCartoonCharactersManually() { 207 * return $( 208 * $(0, "Tarzan"), 209 * $(20, "Jane") 210 * ); 211 * } 212 * public static Object[] provideCartoonCharactersFromDB() { 213 * return cartoonsRepository.loadCharacters(); 214 * } 215 * } 216 * </pre> 217 * 218 * All methods starting with <code>provide</code> are used as parameter 219 * providers. 220 * 221 * <p> 222 * Sometimes though you may want to use just one or few methods of some class to 223 * provide you parameters. This can be done as well like this: 224 * 225 * <pre> 226 * @Test 227 * @Parameters(source = CartoonCharactersProvider.class, method = "cinderellaCharacters,snowwhiteCharacters") 228 * public void testPrincesses(boolean isAPrincess, String characterName) { 229 * ... 230 * } 231 * </pre> 232 * 233 * 234 * <h4 id="d">d. Loading parameters from files</h4> You may be interested in 235 * loading parameters from a file. This is very easy if it's a CSV file with 236 * columns in the same order as test method parameters: 237 * 238 * <pre> 239 * @Test 240 * @FileParameters("cartoon-characters.csv") 241 * public void shouldSurviveInJungle(int yearsInJungle, String person) { 242 * ... 243 * } 244 * </pre> 245 * 246 * But if you want to process the data from the CSV file a bit to use it in the 247 * test method arguments, you 248 * need to use an <code>IdentityMapper</code>. Look: 249 * 250 * <pre> 251 * @Test 252 * @FileParameters(value = "cartoon-characters.csv", mapper = CartoonMapper.class) 253 * public void shouldSurviveInJungle(Person person) { 254 * ... 255 * } 256 * 257 * public class CartoonMapper extends IdentityMapper { 258 * @Override 259 * public Object[] map(Reader reader) { 260 * Object[] map = super.map(reader); 261 * List<Object[]> result = new LinkedList<Object[]>(); 262 * for (Object lineObj : map) { 263 * String line = (String) lineObj; // line in a format just like in the file 264 * result.add(new Object[] { ..... }); // some format edible by the test method 265 * } 266 * return result.toArray(); 267 * } 268 * 269 * } 270 * </pre> 271 * 272 * A CSV files with a header are also supported with the use of <code>CsvWithHeaderMapper</code> class. 273 * 274 * You may also want to use a completely different file format, like excel or 275 * something. Then just parse it yourself: 276 * 277 * <pre> 278 * @Test 279 * @FileParameters(value = "cartoon-characters.xsl", mapper = ExcelCartoonMapper.class) 280 * public void shouldSurviveInJungle(Person person) { 281 * ... 282 * } 283 * 284 * public class CartoonMapper implements DataMapper { 285 * @Override 286 * public Object[] map(Reader fileReader) { 287 * ... 288 * } 289 * } 290 * </pre> 291 * 292 * As you see, you don't need to open or close the file. Just read it from the 293 * reader and parse it the way you wish. 294 * 295 * By default the file is loaded from the file system, relatively to where you start the tests from. But you can also use a resource from 296 * the classpath by prefixing the file name with <code>classpath:</code> 297 * 298 * <h4 id="e">e. Converting parameter values</h4> 299 * Sometimes you want to pass some parameter in one form, but use it in the test in another. Dates are a good example. It's handy to 300 * specify them in the parameters as a String like "2013.01.01", but you'd like to use a Jodatime's LocalDate or JDKs Date in the test 301 * without manually converting the value in the test. This is where the converters become handy. It's enough to annotate a parameter with 302 * a <code>@ConvertParam</code> annotation, give it a converter class and possibly some options (like date format in this case) and 303 * you're done. Here's an example: 304 * <pre> 305 * @Test 306 * @Parameters({ "01.12.2012, A" }) 307 * public void convertMultipleParams( 308 * @ConvertParam(value = StringToDateConverter.class, options = "dd.MM.yyyy") Date date, 309 * @ConvertParam(LetterToASCIIConverter.class) int num) { 310 * 311 * Calendar calendar = Calendar.getInstance(); 312 * calendar.setTime(date); 313 * 314 * assertEquals(2012, calendar.get(Calendar.YEAR)); 315 * assertEquals(11, calendar.get(Calendar.MONTH)); 316 * assertEquals(1, calendar.get(Calendar.DAY_OF_MONTH)); 317 * 318 * assertEquals(65, num); 319 * } 320 * </pre> 321 * 322 * <h3 id="p2">2. Usage with Spring</h3> 323 * <p> 324 * You can easily use JUnitParams together with Spring. The only problem is that 325 * Spring's test framework is based on JUnit runners, and JUnit allows only one 326 * runner to be run at once. Which would normally mean that you could use only 327 * one of Spring or JUnitParams. Luckily we can cheat Spring a little by adding 328 * this to your test class: 329 * 330 * <pre> 331 * private TestContextManager testContextManager; 332 * 333 * @Before 334 * public void init() throws Exception { 335 * this.testContextManager = new TestContextManager(getClass()); 336 * this.testContextManager.prepareTestInstance(this); 337 * } 338 * </pre> 339 * 340 * This lets you use in your tests anything that Spring provides in its test 341 * framework. 342 * 343 * <h3 id="p3">3. Other options</h3> 344 * <h4> Enhancing test case description</h4> 345 * You can use <code>TestCaseName</code> annotation to provide template of the individual test case name: 346 * <pre> 347 * @TestCaseName("factorial({0}) = {1}") 348 * @Parameters({ "1,1"}) 349 * public void fractional_test(int argument, int result) { } 350 * </pre> 351 * Will be displayed as 'fractional(1)=1' 352 * <h4>Customizing how parameter objects are shown in IDE</h4> 353 * <p> 354 * Tests show up in your IDE as a tree with test class name being the root, test 355 * methods being nodes, and parameter sets being the leaves. If you want to 356 * customize the way an parameter object is shown, create a <b>toString</b> 357 * method for it. 358 * <h4>Empty parameter sets</h4> 359 * <p> 360 * If you create a parameterised test, but won't give it any parameter sets, it 361 * will be ignored and you'll be warned about it. 362 * <h4>Parameterised test with no parameters</h4> 363 * <p> 364 * If for some reason you want to have a normal non-parameterised method to be 365 * annotated with @Parameters, then fine, you can do it. But it will be ignored 366 * then, since there won't be any params for it, and parameterised tests need 367 * parameters to execute properly (parameters are a part of test setup, right?) 368 * <h4>JUnit Rules</h4> 369 * <p> 370 * The runner for parameterised test is trying to keep all the @Rule's running, 371 * but if something doesn't work - let me know. It's pretty tricky, since the 372 * rules in JUnit are chained, but the chain is kind of... unstructured, so 373 * sometimes I need to guess how to call the next element in chain. If you have 374 * your own rule, make sure it has a field of type Statement which is the next 375 * statement in chain to call. 376 * <h4>Test inheritance</h4> 377 * <p> 378 * Although usually a bad idea, since it makes tests less readable, sometimes 379 * inheritance is the best way to remove repetitions from tests. JUnitParams is 380 * fine with inheritance - you can define a common test in the superclass, and 381 * have separate parameters provider methods in the subclasses. Also the other 382 * way around is ok, you can define parameter providers in superclass and have 383 * tests in subclasses uses them as their input. 384 * 385 * @author Pawel Lipinski (lipinski.pawel@gmail.com) 386 */ 387 public class JUnitParamsRunner extends BlockJUnit4ClassRunner { 388 389 private final MethodBlockSupplier methodBlockSupplier; 390 JUnitParamsRunner(Class<?> klass)391 public JUnitParamsRunner(Class<?> klass) throws InitializationError { 392 super(klass); 393 methodBlockSupplier = new MethodBlockSupplier() { 394 @Override 395 public Statement getMethodBlock(InvokableFrameworkMethod method) { 396 return methodBlock(method); 397 } 398 }; 399 } 400 401 @Override collectInitializationErrors(List<Throwable> errors)402 protected void collectInitializationErrors(List<Throwable> errors) { 403 super.validateFields(errors); 404 for (Throwable throwable : errors) 405 throwable.printStackTrace(); 406 } 407 408 @Override runChild(FrameworkMethod method, RunNotifier notifier)409 protected void runChild(FrameworkMethod method, RunNotifier notifier) { 410 DescribableFrameworkMethod describableMethod = getDescribableMethod(method); 411 if (handleIgnored(describableMethod, notifier)) 412 return; 413 414 if (method instanceof ParameterisedFrameworkMethod) { 415 ParameterisedFrameworkMethod parameterisedFrameworkMethod = 416 (ParameterisedFrameworkMethod) method; 417 418 List<InstanceFrameworkMethod> methods = parameterisedFrameworkMethod.getMethods(); 419 for (InstanceFrameworkMethod frameworkMethod : methods) { 420 frameworkMethod.run(methodBlockSupplier, notifier); 421 } 422 } 423 else if (describableMethod instanceof InvokableFrameworkMethod) { 424 ((InvokableFrameworkMethod) describableMethod).run(methodBlockSupplier, notifier); 425 } 426 else { 427 throw new IllegalStateException( 428 "Unsupported FrameworkMethod class: " + method.getClass()); 429 } 430 } 431 432 /** 433 * Check that the supplied method is one that was originally in the list returned by 434 * {@link #computeTestMethods()}. 435 * 436 * @param method the method, must be an instance of {@link DescribableFrameworkMethod} 437 * @return the supplied method cast to {@link DescribableFrameworkMethod} 438 * @throws IllegalArgumentException if the supplied method is not a 439 * {@link DescribableFrameworkMethod} 440 */ getDescribableMethod(FrameworkMethod method)441 private DescribableFrameworkMethod getDescribableMethod(FrameworkMethod method) { 442 if (!(method instanceof DescribableFrameworkMethod)) { 443 throw new IllegalArgumentException( 444 "Unsupported FrameworkMethod class: " + method.getClass() 445 + ", expected a DescribableFrameworkMethod subclass"); 446 } 447 448 return (DescribableFrameworkMethod) method; 449 } 450 handleIgnored(DescribableFrameworkMethod method, RunNotifier notifier)451 private boolean handleIgnored(DescribableFrameworkMethod method, RunNotifier notifier) { 452 // A parameterised method that is ignored (either due to @Ignore or due to empty parameters) 453 // is treated as if it was a non-parameterised method. 454 boolean ignored = (method instanceof NonParameterisedFrameworkMethod) 455 && ((NonParameterisedFrameworkMethod) method).isIgnored(); 456 if (ignored) 457 notifier.fireTestIgnored(method.getDescription()); 458 459 return ignored; 460 } 461 462 @Override computeTestMethods()463 protected List<FrameworkMethod> computeTestMethods() { 464 return TestMethod.listFrom(getTestClass()); 465 } 466 467 @Override methodInvoker(FrameworkMethod method, Object test)468 protected Statement methodInvoker(FrameworkMethod method, Object test) { 469 if (method instanceof InvokableFrameworkMethod) { 470 return ((InvokableFrameworkMethod) method).getInvokeStatement(test); 471 } 472 throw new IllegalStateException( 473 "Unsupported FrameworkMethod class: " + method.getClass() 474 + ", expected an InvokableFrameworkMethod subclass"); 475 } 476 477 @Override describeChild(FrameworkMethod method)478 protected Description describeChild(FrameworkMethod method) { 479 return getDescribableMethod(method).getDescription(); 480 } 481 482 /** 483 * Shortcut for returning an array of objects. All parameters passed to this 484 * method are returned in an <code>Object[]</code> array. 485 * 486 * Should not be used to create var-args arrays, because of the way Java resolves 487 * var-args for objects and primitives. 488 * 489 * @deprecated This method is no longer supported. It might be removed in future 490 * as it does not support all cases (especially var-args). Create arrays using 491 * <code>new Object[]{}</code> instead. 492 * 493 * @param params 494 * Values to be returned in an <code>Object[]</code> array. 495 * @return Values passed to this method. 496 */ 497 @Deprecated $(Object... params)498 public static Object[] $(Object... params) { 499 return params; 500 } 501 } 502