Home | History | Annotate | Download | only in junitparams
      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>&#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 (at) gmail.com)
    386  */
    387 public 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