TestParameterInjector ===================== [Link to Javadoc.](https://google.github.io/TestParameterInjector/docs/latest/) ## Introduction `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](https://github.com/junit-team/junit4/wiki/parameterized-tests) and [JUnitParams](https://github.com/Pragmatists/JUnitParams). We believe `TestParameterInjector` is an improvement of those because it is more powerful and simpler to use. [This blogpost](https://opensource.googleblog.com/2021/03/introducing-testparameterinjector.html) goes into a bit more detail about how `TestParameterInjector` compares to other frameworks used at Google. ## Getting started ### JUnit4 To start using `TestParameterInjector` right away, copy the following snippet: ```java 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: ```xml com.google.testparameterinjector test-parameter-injector 1.15 test ``` or see [this maven.org page](https://search.maven.org/artifact/com.google.testparameterinjector/test-parameter-injector) for instructions for other build tools. ### JUnit5 (Jupiter)
Click to expand To start using `TestParameterInjector` right away, copy the following snippet: ```java 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: ```xml com.google.testparameterinjector test-parameter-injector-junit5 1.15 test ``` or see [this maven.org page](https://search.maven.org/artifact/com.google.testparameterinjector/test-parameter-injector-junit5) for instructions for other build tools.
## Basics **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 combinations #### Parameterizing a single test method The simplest way to use this library is to use `@TestParameter`. For example: ```java @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 to `true` - One with `isOwner` set to `false` 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: ```java @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. #### Supported types The following examples show most of the supported types. See the `@TestParameter` javadoc for more details. ```java // 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: ```java @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: ```java @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: ```java @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: > > ```java > @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] > ``` ### Filtering unwanted parameters 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](https://truth.dev/): ```java 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. ## Advanced usage **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`. ### Dynamic parameter generation for `@TestParameter` Instead of providing a list of parsable strings, you can implement your own `TestParameterValuesProvider` as follows: ```java 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 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. ### Dynamic parameter generation for `@TestParameters` Instead of providing a YAML mapping of parameters, you can implement your own `TestParametersValuesProvider` as follows: ```java @Test @TestParameters(valuesProvider = IsAdultValueProvider.class) public void personIsAdult(int age, boolean expectIsAdult) { ... } static final class IsAdultValueProvider implements TestParametersValuesProvider { @Override public ImmutableList 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() ); } } ```