7.7 KiB
TestParameterInjector
Introduction
TestParameterInjector
is a JUnit4 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.
Getting started
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.4</version>
</dependency>
or see this maven.org page for instructions for other build tools.
Basics
@TestParameter
for testing all combinations
Parameterizing a single test method
The 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:
- One with
isOwner
set totrue
- One with
isOwner
set tofalse
When running the tests, the result will show the following test names:
MyTest#test[isOwner=true]
MyTest#test[isOwner=false]
Parameterizing the whole class
@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).
Supported types
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;
For non-primitive types (e.g. String, enums, bytes), "null"
is always parsed as the null
reference.
Multiple parameters: All combinations are run
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 a test enum for enumerating more complex parameter combinations
Use this strategy if you want to:
- Explicitly specify the combination of parameters
- or your parameters are too large to be encoded in a
String
in a readable way
Example:
@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 parameters
You can also explicitly enumerate the sets of test parameters via a list of YAML mappings:
@Test
@TestParameters({
"{age: 17, expectIsAdult: false}",
"{age: 22, expectIsAdult: true}",
})
public void personIsAdult(int age, boolean expectIsAdult) { ... }
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.
Advanced usage
Dynamic parameter generation for @TestParameter
Instead of providing a list of parsable strings, you can implement your own
TestParameterValuesProvider
as follows:
@Test
public void matchesAllOf_throwsOnNull(
@TestParameter(valuesProvider = CharMatcherProvider.class) CharMatcher charMatcher) {
assertThrows(NullPointerException.class, () -> charMatcher.matchesAllOf(null));
}
private static final class CharMatcherProvider implements TestParameterValuesProvider {
@Override
public List<CharMatcher> provideValues() {
return ImmutableList.of(CharMatcher.any(), CharMatcher.ascii(), CharMatcher.whitespace());
}
}
Note that provideValues()
dynamically construct the returned list, e.g. by
reading a file. There are no restrictions on the object types returned, but note
that toString()
will be used for the test names.
Dynamic parameter generation for @TestParameters
Instead of providing a YAML mapping of parameters, you can implement your own
TestParametersValuesProvider
as follows:
@Test
@TestParameters(valuesProvider = IsAdultValueProvider.class)
public void personIsAdult(int age, boolean expectIsAdult) { ... }
static final class IsAdultValueProvider implements TestParametersValuesProvider {
@Override public ImmutableList<TestParametersValues> provideValues() {
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()
);
}
}