1package junitparams;
2
3import java.util.List;
4
5import junitparams.internal.MethodBlockSupplier;
6import org.junit.runner.Description;
7import org.junit.runner.notification.RunNotifier;
8import org.junit.runners.BlockJUnit4ClassRunner;
9import org.junit.runners.model.FrameworkMethod;
10import org.junit.runners.model.InitializationError;
11import org.junit.runners.model.Statement;
12
13import junitparams.internal.DescribableFrameworkMethod;
14import junitparams.internal.InstanceFrameworkMethod;
15import junitparams.internal.InvokableFrameworkMethod;
16import junitparams.internal.NonParameterisedFrameworkMethod;
17import junitparams.internal.ParameterisedFrameworkMethod;
18import 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>&#064;RunWith(JUnitParamsRunner.class)</code> and place
25 * <code>&#064;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 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#a">a. Parameterising tests via values
31 * in annotation</a><br>
32 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#b">b. Parameterising tests via a
33 * method that returns parameter values</a><br>
34 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#c">c. Parameterising tests via
35 * external classes</a><br>
36 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#d">d. Loading parameters from files</a><br>
37 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<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 *   &#064;Test
59 *   &#064;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 * &#064;Test
70 * &#064;Parameters({ &quot;FROM_JUNGLE&quot;, &quot;FROM_CITY&quot; })
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 *   &#064;Test
84 *   &#064;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 *   &#064;Test
106 *   &#064;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 *   &#064;Test
132 *   &#064;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 *   &#064;Test
150 *   &#064;Parameters
151 *   public void cartoonCharacters(Person character) {
152 *       ...
153 *   }
154 *   private List&lt;Person&gt; 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 *   &#064;Test
169 *   &#064;Parameters
170 *   public void cartoonCharacters(Person character) {
171 *       ...
172 *   }
173 *   private List&lt;?&gt; 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 *   &#064;Test
200 *   &#064;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 *   &#064;Test
227 *   &#064;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 *   &#064;Test
240 *   &#064;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 *   &#064;Test
252 *   &#064;FileParameters(value = "cartoon-characters.csv", mapper = CartoonMapper.class)
253 *   public void shouldSurviveInJungle(Person person) {
254 *       ...
255 *   }
256 *
257 *   public class CartoonMapper extends IdentityMapper {
258 *     &#064;Override
259 *     public Object[] map(Reader reader) {
260 *         Object[] map = super.map(reader);
261 *         List&lt;Object[]&gt; result = new LinkedList&lt;Object[]&gt;();
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 *   &#064;Test
279 *   &#064;FileParameters(value = "cartoon-characters.xsl", mapper = ExcelCartoonMapper.class)
280 *   public void shouldSurviveInJungle(Person person) {
281 *       ...
282 *   }
283 *
284 *   public class CartoonMapper implements DataMapper {
285 *     &#064;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>&#064;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 *     &#064;Test
306 *     &#064;Parameters({ "01.12.2012, A" })
307 *     public void convertMultipleParams(
308 *                  &#064;ConvertParam(value = StringToDateConverter.class, options = "dd.MM.yyyy") Date date,
309 *                  &#064;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 * &#064;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 *     &#064;TestCaseName("factorial({0}) = {1}")
348 *     &#064;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 */
387public class JUnitParamsRunner extends BlockJUnit4ClassRunner {
388
389    private final MethodBlockSupplier methodBlockSupplier;
390
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
402    protected void collectInitializationErrors(List<Throwable> errors) {
403        super.validateFields(errors);
404        for (Throwable throwable : errors)
405            throwable.printStackTrace();
406    }
407
408    @Override
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     */
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
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
463    protected List<FrameworkMethod> computeTestMethods() {
464        return TestMethod.listFrom(getTestClass());
465    }
466
467    @Override
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
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
498    public static Object[] $(Object... params) {
499        return params;
500    }
501}
502