package junitparams; import java.util.List; import junitparams.internal.MethodBlockSupplier; import org.junit.runner.Description; import org.junit.runner.notification.RunNotifier; import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.model.FrameworkMethod; import org.junit.runners.model.InitializationError; import org.junit.runners.model.Statement; import junitparams.internal.DescribableFrameworkMethod; import junitparams.internal.InstanceFrameworkMethod; import junitparams.internal.InvokableFrameworkMethod; import junitparams.internal.NonParameterisedFrameworkMethod; import junitparams.internal.ParameterisedFrameworkMethod; import junitparams.internal.TestMethod; /** *
* 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.
*
* 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) { * } ** *
* 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. * *
* 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) { * ... * } ** * *
* @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:
*
* @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); * } ** *
* 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. * *
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' *
* 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. *
* 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. *
* 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?) *
* 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. *
* 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)
*/
public class JUnitParamsRunner extends BlockJUnit4ClassRunner {
private final MethodBlockSupplier methodBlockSupplier;
public JUnitParamsRunner(Class> klass) throws InitializationError {
super(klass);
methodBlockSupplier = new MethodBlockSupplier() {
@Override
public Statement getMethodBlock(InvokableFrameworkMethod method) {
return methodBlock(method);
}
};
}
@Override
protected void collectInitializationErrors(ListObject[]
array.
*
* Should not be used to create var-args arrays, because of the way Java resolves
* var-args for objects and primitives.
*
* @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.
*
* @param params
* Values to be returned in an Object[]
array.
* @return Values passed to this method.
*/
@Deprecated
public static Object[] $(Object... params) {
return params;
}
}