junitparams
Class JUnitParamsRunner

java.lang.Object
  extended by org.junit.runner.Runner
      extended by org.junit.runners.ParentRunner<org.junit.runners.model.FrameworkMethod>
          extended by org.junit.runners.BlockJUnit4ClassRunner
              extended by junitparams.JUnitParamsRunner
All Implemented Interfaces:
org.junit.runner.Describable, org.junit.runner.manipulation.Filterable, org.junit.runner.manipulation.Sortable

public class JUnitParamsRunner
extends org.junit.runners.BlockJUnit4ClassRunner

JUnitParams


This is a JUnit runner for parameterised tests that don't suck. Annotate your test class with @RunWith(JUnitParamsRunner.class) and place @Parameters annotation on each test method which requires parameters. Nothing more needed - no special structure, no dirty tricks.


Contents

1. Parameterising tests
     a. Parameterising tests via values in annotation
     b. Parameterising tests via a method that returns parameter values
     c. Parameterising tests via external classes
     d. Loading parameters from files
     e. Converting parameter values
2. Usage with Spring
3. Other options

1. Parameterising tests

Parameterised tests are a great way to limit the amount of test code when you need to test the same code under different conditions. Ever tried to do it with standard JUnit tools like Parameterized runner or Theories? I always thought they're so awkward to use, that I've written this library to help all those out there who'd like to have a handy tool. So here we go. There are a few different ways to use JUnitParams, I will try to show you all of them here.

a. Parameterising tests via values in annotation

You can parameterise your test with values defined in annotations. Just pass sets of test method argument values as an array of Strings, where each string contains the argument values separated by a comma or a pipe "|".

   @Test
   @Parameters({ "20, Tarzan", "0, Jane" })
   public void cartoonCharacters(int yearsInJungle, String person) {
       ...
   }
 
Sometimes you may be interested in passing enum values as parameters, then you can just write them as Strings like this:
 @Test
 @Parameters({ "FROM_JUNGLE", "FROM_CITY" })
 public void passEnumAsParam(PersonType person) {
 }
 

b. Parameterising tests via a method that returns parameter values

Obviously passing parameters as strings is handy only for trivial situations, that's why for normal cases you have a method that gives you a collection of parameters:

   @Test
   @Parameters(method = "cartoonCharacters")
   public void cartoonCharacters(int yearsInJungle, String person) {
       ...
   }
   private Object[] cartoonCharacters() {
      return $(
          $(0, "Tarzan"),
          $(20, "Jane")
      );
   }
 
Where $(...) is a static method defined in JUnitParamsRunner class, which returns its parameters as a Object[] array. Just a shortcut, so that you don't need to write the ugly new Object[] {} kind of stuff.

method can take more than one method name - you can pass as many of them as you want, separated by commas. This enables you to divide your test cases e.g. into categories.

   @Test
   @Parameters(method = "menCharactes, womenCharacters")
   public void cartoonCharacters(int yearsInJungle, String person) {
       ...
   }
   private Object[] menCharacters() {
      return $(
          $(20, "Tarzan"),
          $(2, "Chip"),
          $(2, "Dale")
      );
   }
   private Object[] womenCharacters() {
      return $(
          $(0, "Jane"),
          $(18, "Pocahontas")
      );
   }
 

The method argument of a @Parameters annotation can be ommited if the method that provides parameters has a the same name as the test, but prefixed by parametersFor. So our example would look like this:

   @Test
   @Parameters
   public void cartoonCharacters(int yearsInJungle, String person) {
       ...
   }
   private Object[] parametersForCartoonCharacters() {
      return $(
          $(0, "Tarzan"),
          $(20, "Jane")
      );
   }
 

If you don't like returning untyped values and arrays, you can equally well return any Iterable of concrete objects:

   @Test
   @Parameters
   public void cartoonCharacters(Person character) {
       ...
   }
   private List<Person> parametersForCartoonCharacters() {
      return Arrays.asList(
          new Person(0, "Tarzan"),
          new Person(20, "Jane")
      );
   }
 
If we had more than just two Person's to make, we would get redundant, so JUnitParams gives you a simplified way of creating objects to be passed as params. You can omit the creation of the objects and just return their constructor argument values like this:
   @Test
   @Parameters
   public void cartoonCharacters(Person character) {
       ...
   }
   private List<?> parametersForCartoonCharacters() {
      return Arrays.asList(
          $(0, "Tarzan"),
          $(20, "Jane")
      );
   }
 
And JUnitParams will invoke the appropriate constructor (new Person(int age, String name) in this case.) If you want to use it, watch out! Automatic refactoring of constructor arguments won't be working here!

You can also define methods that provide parameters in subclasses and use them in test methods defined in superclasses, as well as redefine data providing methods in subclasses to be used by test method defined in a superclass. That you can doesn't mean you should. Inheritance in tests is usually a code smell (readability hurts), so make sure you know what you're doing.

c. Parameterising tests via external classes

For more complex cases you may want to externalise the method that provides parameters or use more than one method to provide parameters to a single test method. You can easily do that like this:

   @Test
   @Parameters(source = CartoonCharactersProvider.class)
   public void testReadyToLiveInJungle(int yearsInJungle, String person) {
       ...
   }
   ...
   class CartoonCharactersProvider {
      public static Object[] provideCartoonCharactersManually() {
          return $(
              $(0, "Tarzan"),
              $(20, "Jane")
          );
      }
      public static Object[] provideCartoonCharactersFromDB() {
          return cartoonsRepository.loadCharacters();
      }
   }
 
All methods starting with provide are used as parameter providers.

Sometimes though you may want to use just one or few methods of some class to provide you parameters. This can be done as well like this:

   @Test
   @Parameters(source = CartoonCharactersProvider.class, method = "cinderellaCharacters,snowwhiteCharacters")
   public void testPrincesses(boolean isAPrincess, String characterName) {
       ...
   }
 

d. Loading parameters from files

You may be interested in loading parameters from a file. This is very easy if it's a CSV file with columns in the same order as test method parameters:
   @Test
   @FileParameters("cartoon-characters.csv")
   public void shouldSurviveInJungle(int yearsInJungle, String person) {
       ...
   }
 
But if you want to process the data from the CSV file a bit to use it in the test method arguments, you need to use an IdentityMapper. Look:
   @Test
   @FileParameters(value = "cartoon-characters.csv", mapper = CartoonMapper.class)
   public void shouldSurviveInJungle(Person person) {
       ...
   }

   public class CartoonMapper extends IdentityMapper {
     @Override
     public Object[] map(Reader reader) {
         Object[] map = super.map(reader);
         List<Object[]> result = new LinkedList<Object[]>();
         for (Object lineObj : map) {
             String line = (String) lineObj; // line in a format just like in the file
             result.add(new Object[] { ..... }); // some format edible by the test method
         }
         return result.toArray();
     }

 }
 
A CSV files with a header are also supported with the use of CsvWithHeaderMapper class. You may also want to use a completely different file format, like excel or something. Then just parse it yourself:
   @Test
   @FileParameters(value = "cartoon-characters.xsl", mapper = ExcelCartoonMapper.class)
   public void shouldSurviveInJungle(Person person) {
       ...
   }

   public class CartoonMapper implements DataMapper {
     @Override
     public Object[] map(Reader fileReader) {
         ...
     }
 }
 
As you see, you don't need to open or close the file. Just read it from the reader and parse it the way you wish. 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 the classpath by prefixing the file name with classpath:

e. Converting parameter values

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 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 without manually converting the value in the test. This is where the converters become handy. It's enough to annotate a parameter with a @ConvertParam annotation, give it a converter class and possibly some options (like date format in this case) and you're done. Here's an example:
     @Test
     @Parameters({ "01.12.2012, A" })
     public void convertMultipleParams(
                  @ConvertParam(value = StringToDateConverter.class, options = "dd.MM.yyyy") Date date,
                  @ConvertParam(LetterToASCIIConverter.class) int num) {

         Calendar calendar = Calendar.getInstance();
         calendar.setTime(date);

         assertEquals(2012, calendar.get(Calendar.YEAR));
         assertEquals(11, calendar.get(Calendar.MONTH));
         assertEquals(1, calendar.get(Calendar.DAY_OF_MONTH));

         assertEquals(65, num);
     }
 

2. Usage with Spring

You can easily use JUnitParams together with Spring. The only problem is that Spring's test framework is based on JUnit runners, and JUnit allows only one runner to be run at once. Which would normally mean that you could use only one of Spring or JUnitParams. Luckily we can cheat Spring a little by adding this to your test class:

 private TestContextManager testContextManager;

 @Before
 public void init() throws Exception {
     this.testContextManager = new TestContextManager(getClass());
     this.testContextManager.prepareTestInstance(this);
 }
 
This lets you use in your tests anything that Spring provides in its test framework.

3. Other options

Enhancing test case description

You can use TestCaseName annotation to provide template of the individual test case name:
     @TestCaseName("factorial({0}) = {1}")
     @Parameters({ "1,1"})
     public void fractional_test(int argument, int result) { }
 
Will be displayed as 'fractional(1)=1'

Customizing how parameter objects are shown in IDE

Tests show up in your IDE as a tree with test class name being the root, test methods being nodes, and parameter sets being the leaves. If you want to customize the way an parameter object is shown, create a toString method for it.

Empty parameter sets

If you create a parameterised test, but won't give it any parameter sets, it will be ignored and you'll be warned about it.

Parameterised test with no parameters

If for some reason you want to have a normal non-parameterised method to be annotated with @Parameters, then fine, you can do it. But it will be ignored then, since there won't be any params for it, and parameterised tests need parameters to execute properly (parameters are a part of test setup, right?)

JUnit Rules

The runner for parameterised test is trying to keep all the @Rule's running, but if something doesn't work - let me know. It's pretty tricky, since the rules in JUnit are chained, but the chain is kind of... unstructured, so sometimes I need to guess how to call the next element in chain. If you have your own rule, make sure it has a field of type Statement which is the next statement in chain to call.

Test inheritance

Although usually a bad idea, since it makes tests less readable, sometimes inheritance is the best way to remove repetitions from tests. JUnitParams is fine with inheritance - you can define a common test in the superclass, and have separate parameters provider methods in the subclasses. Also the other way around is ok, you can define parameter providers in superclass and have tests in subclasses uses them as their input.

Author:
Pawel Lipinski (lipinski.pawel@gmail.com)

Constructor Summary
JUnitParamsRunner(Class<?> klass)
           
 
Method Summary
static Object[] $(Object... params)
          Deprecated. This method is no longer supported. It might be removed in future as it does not support all cases (especially var-args). Create arrays using new Object[]{} instead.
protected  void collectInitializationErrors(List<Throwable> errors)
           
protected  List<org.junit.runners.model.FrameworkMethod> computeTestMethods()
           
 org.junit.runner.Description describeMethod(org.junit.runners.model.FrameworkMethod method)
           
 void filter(org.junit.runner.manipulation.Filter filter)
           
 org.junit.runner.Description getDescription()
           
protected  org.junit.runners.model.Statement methodInvoker(org.junit.runners.model.FrameworkMethod method, Object test)
           
protected  void runChild(org.junit.runners.model.FrameworkMethod method, org.junit.runner.notification.RunNotifier notifier)
           
 
Methods inherited from class org.junit.runners.BlockJUnit4ClassRunner
createTest, describeChild, getChildren, getTestRules, isIgnored, methodBlock, possiblyExpectingExceptions, rules, testName, validateConstructor, validateFields, validateInstanceMethods, validateNoNonStaticInnerClass, validateOnlyOneConstructor, validateTestMethods, validateZeroArgConstructor, withAfters, withBefores, withPotentialTimeout
 
Methods inherited from class org.junit.runners.ParentRunner
childrenInvoker, classBlock, classRules, createTestClass, getName, getRunnerAnnotations, getTestClass, run, runLeaf, setScheduler, sort, validatePublicVoidNoArgMethods, withAfterClasses, withBeforeClasses
 
Methods inherited from class org.junit.runner.Runner
testCount
 
Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
 

Constructor Detail

JUnitParamsRunner

public JUnitParamsRunner(Class<?> klass)
                  throws org.junit.runners.model.InitializationError
Throws:
org.junit.runners.model.InitializationError
Method Detail

filter

public void filter(org.junit.runner.manipulation.Filter filter)
            throws org.junit.runner.manipulation.NoTestsRemainException
Specified by:
filter in interface org.junit.runner.manipulation.Filterable
Overrides:
filter in class org.junit.runners.ParentRunner<org.junit.runners.model.FrameworkMethod>
Throws:
org.junit.runner.manipulation.NoTestsRemainException

collectInitializationErrors

protected void collectInitializationErrors(List<Throwable> errors)
Overrides:
collectInitializationErrors in class org.junit.runners.BlockJUnit4ClassRunner

runChild

protected void runChild(org.junit.runners.model.FrameworkMethod method,
                        org.junit.runner.notification.RunNotifier notifier)
Overrides:
runChild in class org.junit.runners.BlockJUnit4ClassRunner

computeTestMethods

protected List<org.junit.runners.model.FrameworkMethod> computeTestMethods()
Overrides:
computeTestMethods in class org.junit.runners.BlockJUnit4ClassRunner

methodInvoker

protected org.junit.runners.model.Statement methodInvoker(org.junit.runners.model.FrameworkMethod method,
                                                          Object test)
Overrides:
methodInvoker in class org.junit.runners.BlockJUnit4ClassRunner

getDescription

public org.junit.runner.Description getDescription()
Specified by:
getDescription in interface org.junit.runner.Describable
Overrides:
getDescription in class org.junit.runners.ParentRunner<org.junit.runners.model.FrameworkMethod>

describeMethod

public org.junit.runner.Description describeMethod(org.junit.runners.model.FrameworkMethod method)

$

@Deprecated
public static Object[] $(Object... params)
Deprecated. This method is no longer supported. It might be removed in future as it does not support all cases (especially var-args). Create arrays using new Object[]{} instead.

Shortcut for returning an array of objects. All parameters passed to this method are returned in an Object[] array. Should not be used to create var-args arrays, because of the way Java resolves var-args for objects and primitives.

Parameters:
params - Values to be returned in an Object[] array.
Returns:
Values passed to this method.


Copyright © 2017 Pragmatists. All rights reserved.