TestParameterInjector
is a JUnit4 and JUnit5 test runner that runs its test methods for different combinations of field/parameter values.
Parameterized tests are a great way to avoid code duplication between tests and promote high test coverage for data-driven tests.
There are a lot of alternative parameterized test frameworks, such as junit.runners.Parameterized and JUnitParams. We believe TestParameterInjector
is an improvement of those because it is more powerful and simpler to use.
This blogpost goes into a bit more detail about how TestParameterInjector
compares to other frameworks used at Google.
To start using TestParameterInjector
right away, copy the following snippet:
import com.google.testing.junit.testparameterinjector.TestParameterInjector; import com.google.testing.junit.testparameterinjector.TestParameter; @RunWith(TestParameterInjector.class) public class MyTest { @TestParameter boolean isDryRun; @Test public void test1(@TestParameter boolean enableFlag) { // ... } @Test public void test2(@TestParameter MyEnum myEnum) { // ... } enum MyEnum { VALUE_A, VALUE_B, VALUE_C } }
And add the following dependency to your .pom
file:
<dependency> <groupId>com.google.testparameterinjector</groupId> <artifactId>test-parameter-injector</artifactId> <version>1.18</version> <scope>test</scope> </dependency>
or see this maven.org page for instructions for other build tools.
To start using TestParameterInjector
right away, copy the following snippet:
import com.google.testing.junit.testparameterinjector.junit5.TestParameterInjectorTest; import com.google.testing.junit.testparameterinjector.junit5.TestParameter; class MyTest { @TestParameter boolean isDryRun; @TestParameterInjectorTest void test1(@TestParameter boolean enableFlag) { // ... } @TestParameterInjectorTest void test2(@TestParameter MyEnum myEnum) { // ... } enum MyEnum { VALUE_A, VALUE_B, VALUE_C } }
And add the following dependency to your .pom
file:
<dependency> <groupId>com.google.testparameterinjector</groupId> <artifactId>test-parameter-injector-junit5</artifactId> <version>1.18</version> <scope>test</scope> </dependency>
or see this maven.org page for instructions for other build tools.
Note about JUnit4 vs JUnit5:
The code below assumes you're using JUnit4. For JUnit5 users, simply remove the @RunWith
annotation and replace @Test
by @TestParameterInjectorTest
.
@TestParameter
for testing all combinationsThe simplest way to use this library is to use @TestParameter
. For example:
@RunWith(TestParameterInjector.class) public class MyTest { @Test public void test(@TestParameter boolean isOwner) {...} }
In this example, two tests will be automatically generated by the test framework:
isOwner
set to true
isOwner
set to false
When running the tests, the result will show the following test names:
MyTest#test[isOwner=true] MyTest#test[isOwner=false]
@TestParameter
can also annotate a field:
@RunWith(TestParameterInjector.class) public class MyTest { @TestParameter private boolean isOwner; @Test public void test1() {...} @Test public void test2() {...} }
In this example, both test1
and test2
will be run twice (once for each parameter value).
The test runner will set these fields before calling any methods, so it is safe to use such @TestParameter
-annotated fields for setting up other test values and behavior in @Before
methods.
The following examples show most of the supported types. See the @TestParameter
javadoc for more details.
// Enums @TestParameter AnimalEnum a; // Implies all possible values of AnimalEnum @TestParameter({"CAT", "DOG"}) AnimalEnum a; // Implies AnimalEnum.CAT and AnimalEnum.DOG. // Strings @TestParameter({"cat", "dog"}) String animalName; // Java primitives @TestParameter boolean b; // Implies {true, false} @TestParameter({"1", "2", "3"}) int i; @TestParameter({"1", "1.5", "2"}) double d; // Bytes @TestParameter({"!!binary 'ZGF0YQ=='", "some_string"}) byte[] bytes; // Durations (segments of number+unit as shown below) @TestParameter({"1d", "2h", "3min", "4s", "5ms", "6us", "7ns"}) java.time.Duration d; @TestParameter({"1h30min", "-2h10min20s", "1.5h", ".5s", "0"}) java.time.Duration d;
For non-primitive types (e.g. String, enums, bytes), "null"
is always parsed as the null
reference.
If there are multiple @TestParameter
-annotated values applicable to one test method, the test is run for all possible combinations of those values. Example:
@RunWith(TestParameterInjector.class) public class MyTest { @TestParameter private boolean a; @Test public void test1(@TestParameter boolean b, @TestParameter boolean c) { // Run for these combinations: // (a=false, b=false, c=false) // (a=false, b=false, c=true ) // (a=false, b=true, c=false) // (a=false, b=true, c=true ) // (a=true, b=false, c=false) // (a=true, b=false, c=true ) // (a=true, b=true, c=false) // (a=true, b=true, c=true ) } }
If you want to explicitly define which combinations are run, see the next sections.
Use this strategy if you want to:
String
in a readable wayExample:
@RunWith(TestParameterInjector.class) class MyTest { enum FruitVolumeTestCase { APPLE(Fruit.newBuilder().setName("Apple").setShape(SPHERE).build(), /* expectedVolume= */ 3.1), BANANA(Fruit.newBuilder().setName("Banana").setShape(CURVED).build(), /* expectedVolume= */ 2.1), MELON(Fruit.newBuilder().setName("Melon").setShape(SPHERE).build(), /* expectedVolume= */ 6); final Fruit fruit; final double expectedVolume; FruitVolumeTestCase(Fruit fruit, double expectedVolume) { ... } } @Test public void calculateVolume_success(@TestParameter FruitVolumeTestCase fruitVolumeTestCase) { assertThat(calculateVolume(fruitVolumeTestCase.fruit)) .isEqualTo(fruitVolumeTestCase.expectedVolume); } }
The enum constant name has the added benefit of making for sensible test names:
MyTest#calculateVolume_success[APPLE] MyTest#calculateVolume_success[BANANA] MyTest#calculateVolume_success[MELON]
@TestParameters
for defining sets of parametersYou can also explicitly enumerate the sets of test parameters via a list of YAML mappings:
@Test @TestParameters("{age: 17, expectIsAdult: false}") @TestParameters("{age: 22, expectIsAdult: true}") public void personIsAdult(int age, boolean expectIsAdult) { ... }
which would generate the following tests:
MyTest#personIsAdult[{age: 17, expectIsAdult: false}] MyTest#personIsAdult[{age: 22, expectIsAdult: true}]
The string format supports the same types as @TestParameter
(e.g. enums). See the @TestParameters
javadoc for more info.
@TestParameters
works in the same way on the constructor, in which case all tests will be run for the given parameter sets.
Tip: Consider setting a custom name if the YAML string is large:
@Test @TestParameters(customName = "teenager", value = "{age: 17, expectIsAdult: false}") @TestParameters(customName = "young adult", value = "{age: 22, expectIsAdult: true}") public void personIsAdult(int age, boolean expectIsAdult) { ... }This will generate the following test names:
MyTest#personIsAdult[teenager] MyTest#personIsAdult[young adult]
Sometimes, you want to exclude a parameter or a combination of parameters. We recommend doing this via JUnit assumptions which is also supported by Truth:
import static com.google.common.truth.TruthJUnit.assume; @Test public void myTest(@TestParameter Fruit fruit) { assume().that(fruit).isNotEqualTo(Fruit.BANANA); // At this point, the test will only run for APPLE and CHERRY. // The BANANA case will silently be ignored. } enum Fruit { APPLE, BANANA, CHERRY }
Note that the above works regardless of what parameterization framework you choose.
Note about JUnit4 vs JUnit5:
The code below assumes you're using JUnit4. For JUnit5 users, simply remove the @RunWith
annotation and replace @Test
by @TestParameterInjectorTest
.
@TestParameter
Instead of providing a list of parsable strings, you can implement your own TestParameterValuesProvider
as follows:
import com.google.testing.junit.testparameterinjector.TestParameterValuesProvider; @Test public void matchesAllOf_throwsOnNull( @TestParameter(valuesProvider = CharMatcherProvider.class) CharMatcher charMatcher) { assertThrows(NullPointerException.class, () -> charMatcher.matchesAllOf(null)); } private static final class CharMatcherProvider extends TestParameterValuesProvider { @Override public List<CharMatcher> provideValues(Context context) { return ImmutableList.of(CharMatcher.any(), CharMatcher.ascii(), CharMatcher.whitespace()); } }
Notes:
The provideValues()
method can dynamically construct the returned list, e.g. by reading a file.
There are no restrictions on the object types returned.
The provideValues()
method is called before @BeforeClass
, so don't rely on any static state initialized in there.
The returned objects' toString()
will be used for the test names. If you want to customize the value names, you can do that as follows:
private static final class FruitProvider extends TestParameterValuesProvider { @Override public List<?> provideValues(Context context) { return ImmutableList.of( value(new Apple()).withName("apple"), value(new Banana()).withName("banana")); } }
The given Context
contains the test class and other annotations on the @TestParameter
-annotated parameter/field. This allows more generic providers that take into account custom annotations with extra data, or the implementation of abstract methods on a base test class.
@TestParameters
Instead of providing a YAML mapping of parameters, you can implement your own TestParametersValuesProvider
as follows:
import com.google.testing.junit.testparameterinjector.TestParametersValuesProvider; import com.google.testing.junit.testparameterinjector.TestParameters.TestParametersValues; @Test @TestParameters(valuesProvider = IsAdultValueProvider.class) public void personIsAdult(int age, boolean expectIsAdult) { ... } static final class IsAdultValueProvider extends TestParametersValuesProvider { @Override public ImmutableList<TestParametersValues> provideValues(Context context) { return ImmutableList.of( TestParametersValues.builder() .name("teenager") .addParameter("age", 17) .addParameter("expectIsAdult", false) .build(), TestParametersValues.builder() .name("young adult") .addParameter("age", 22) .addParameter("expectIsAdult", true) .build() ); } }