1TestParameterInjector 2===================== 3 4[Link to Javadoc.](https://google.github.io/TestParameterInjector/docs/latest/) 5 6## Introduction 7 8`TestParameterInjector` is a JUnit4 and JUnit5 test runner that runs its test methods for 9different combinations of field/parameter values. 10 11Parameterized tests are a great way to avoid code duplication between tests and 12promote high test coverage for data-driven tests. 13 14There are a lot of alternative parameterized test frameworks, such as 15[junit.runners.Parameterized](https://github.com/junit-team/junit4/wiki/parameterized-tests) 16and [JUnitParams](https://github.com/Pragmatists/JUnitParams). We believe 17`TestParameterInjector` is an improvement of those because it is more powerful 18and simpler to use. 19 20[This blogpost](https://opensource.googleblog.com/2021/03/introducing-testparameterinjector.html) 21goes into a bit more detail about how `TestParameterInjector` compares to other 22frameworks used at Google. 23 24## Getting started 25 26### JUnit4 27 28To start using `TestParameterInjector` right away, copy the following snippet: 29 30```java 31import com.google.testing.junit.testparameterinjector.TestParameterInjector; 32import com.google.testing.junit.testparameterinjector.TestParameter; 33 34@RunWith(TestParameterInjector.class) 35public class MyTest { 36 37 @TestParameter boolean isDryRun; 38 39 @Test public void test1(@TestParameter boolean enableFlag) { 40 // ... 41 } 42 43 @Test public void test2(@TestParameter MyEnum myEnum) { 44 // ... 45 } 46 47 enum MyEnum { VALUE_A, VALUE_B, VALUE_C } 48} 49``` 50 51And add the following dependency to your `.pom` file: 52 53```xml 54<dependency> 55 <groupId>com.google.testparameterinjector</groupId> 56 <artifactId>test-parameter-injector</artifactId> 57 <version>1.15</version> 58 <scope>test</scope> 59</dependency> 60``` 61 62or see [this maven.org 63page](https://search.maven.org/artifact/com.google.testparameterinjector/test-parameter-injector) 64for instructions for other build tools. 65 66### JUnit5 (Jupiter) 67<details> 68<summary>Click to expand</summary> 69 70To start using `TestParameterInjector` right away, copy the following snippet: 71 72```java 73import com.google.testing.junit.testparameterinjector.junit5.TestParameterInjectorTest; 74import com.google.testing.junit.testparameterinjector.junit5.TestParameter; 75 76class MyTest { 77 78 @TestParameter boolean isDryRun; 79 80 @TestParameterInjectorTest 81 void test1(@TestParameter boolean enableFlag) { 82 // ... 83 } 84 85 @TestParameterInjectorTest 86 void test2(@TestParameter MyEnum myEnum) { 87 // ... 88 } 89 90 enum MyEnum { VALUE_A, VALUE_B, VALUE_C } 91} 92``` 93 94And add the following dependency to your `.pom` file: 95 96```xml 97<dependency> 98 <groupId>com.google.testparameterinjector</groupId> 99 <artifactId>test-parameter-injector-junit5</artifactId> 100 <version>1.15</version> 101 <scope>test</scope> 102</dependency> 103``` 104 105or see [this maven.org 106page](https://search.maven.org/artifact/com.google.testparameterinjector/test-parameter-injector-junit5) 107for instructions for other build tools. 108 109</details> 110 111## Basics 112 113**Note about JUnit4 vs JUnit5:**<br /> 114The code below assumes you're using JUnit4. For JUnit5 users, simply remove the 115`@RunWith` annotation and replace `@Test` by `@TestParameterInjectorTest`. 116 117### `@TestParameter` for testing all combinations 118 119#### Parameterizing a single test method 120 121The simplest way to use this library is to use `@TestParameter`. For example: 122 123```java 124@RunWith(TestParameterInjector.class) 125public class MyTest { 126 127 @Test 128 public void test(@TestParameter boolean isOwner) {...} 129} 130``` 131 132In this example, two tests will be automatically generated by the test framework: 133 134- One with `isOwner` set to `true` 135- One with `isOwner` set to `false` 136 137When running the tests, the result will show the following test names: 138 139``` 140MyTest#test[isOwner=true] 141MyTest#test[isOwner=false] 142``` 143 144#### Parameterizing the whole class 145 146`@TestParameter` can also annotate a field: 147 148```java 149@RunWith(TestParameterInjector.class) 150public class MyTest { 151 152 @TestParameter private boolean isOwner; 153 154 @Test public void test1() {...} 155 @Test public void test2() {...} 156} 157``` 158 159In this example, both `test1` and `test2` will be run twice (once for each 160parameter value). 161 162The test runner will set these fields before calling any methods, so it is safe 163to use such `@TestParameter`-annotated fields for setting up other test values 164and behavior in `@Before` methods. 165 166#### Supported types 167 168The following examples show most of the supported types. See the `@TestParameter` javadoc for more details. 169 170```java 171// Enums 172@TestParameter AnimalEnum a; // Implies all possible values of AnimalEnum 173@TestParameter({"CAT", "DOG"}) AnimalEnum a; // Implies AnimalEnum.CAT and AnimalEnum.DOG. 174 175// Strings 176@TestParameter({"cat", "dog"}) String animalName; 177 178// Java primitives 179@TestParameter boolean b; // Implies {true, false} 180@TestParameter({"1", "2", "3"}) int i; 181@TestParameter({"1", "1.5", "2"}) double d; 182 183// Bytes 184@TestParameter({"!!binary 'ZGF0YQ=='", "some_string"}) byte[] bytes; 185``` 186 187For non-primitive types (e.g. String, enums, bytes), `"null"` is always parsed as the `null` reference. 188 189#### Multiple parameters: All combinations are run 190 191If there are multiple `@TestParameter`-annotated values applicable to one test 192method, the test is run for all possible combinations of those values. Example: 193 194```java 195@RunWith(TestParameterInjector.class) 196public class MyTest { 197 198 @TestParameter private boolean a; 199 200 @Test public void test1(@TestParameter boolean b, @TestParameter boolean c) { 201 // Run for these combinations: 202 // (a=false, b=false, c=false) 203 // (a=false, b=false, c=true ) 204 // (a=false, b=true, c=false) 205 // (a=false, b=true, c=true ) 206 // (a=true, b=false, c=false) 207 // (a=true, b=false, c=true ) 208 // (a=true, b=true, c=false) 209 // (a=true, b=true, c=true ) 210 } 211} 212``` 213 214If you want to explicitly define which combinations are run, see the next 215sections. 216 217### Use a test enum for enumerating more complex parameter combinations 218 219Use this strategy if you want to: 220 221- Explicitly specify the combination of parameters 222- or your parameters are too large to be encoded in a `String` in a readable 223 way 224 225Example: 226 227```java 228@RunWith(TestParameterInjector.class) 229class MyTest { 230 231 enum FruitVolumeTestCase { 232 APPLE(Fruit.newBuilder().setName("Apple").setShape(SPHERE).build(), /* expectedVolume= */ 3.1), 233 BANANA(Fruit.newBuilder().setName("Banana").setShape(CURVED).build(), /* expectedVolume= */ 2.1), 234 MELON(Fruit.newBuilder().setName("Melon").setShape(SPHERE).build(), /* expectedVolume= */ 6); 235 236 final Fruit fruit; 237 final double expectedVolume; 238 239 FruitVolumeTestCase(Fruit fruit, double expectedVolume) { ... } 240 } 241 242 @Test 243 public void calculateVolume_success(@TestParameter FruitVolumeTestCase fruitVolumeTestCase) { 244 assertThat(calculateVolume(fruitVolumeTestCase.fruit)) 245 .isEqualTo(fruitVolumeTestCase.expectedVolume); 246 } 247} 248``` 249 250The enum constant name has the added benefit of making for sensible test names: 251 252``` 253MyTest#calculateVolume_success[APPLE] 254MyTest#calculateVolume_success[BANANA] 255MyTest#calculateVolume_success[MELON] 256``` 257 258### `@TestParameters` for defining sets of parameters 259 260You can also explicitly enumerate the sets of test parameters via a list of YAML 261mappings: 262 263```java 264@Test 265@TestParameters("{age: 17, expectIsAdult: false}") 266@TestParameters("{age: 22, expectIsAdult: true}") 267public void personIsAdult(int age, boolean expectIsAdult) { ... } 268``` 269 270which would generate the following tests: 271 272``` 273MyTest#personIsAdult[{age: 17, expectIsAdult: false}] 274MyTest#personIsAdult[{age: 22, expectIsAdult: true}] 275``` 276 277The string format supports the same types as `@TestParameter` (e.g. enums). See 278the `@TestParameters` javadoc for more info. 279 280`@TestParameters` works in the same way on the constructor, in which case all 281tests will be run for the given parameter sets. 282 283> Tip: Consider setting a custom name if the YAML string is large: 284> 285> ```java 286> @Test 287> @TestParameters(customName = "teenager", value = "{age: 17, expectIsAdult: false}") 288> @TestParameters(customName = "young adult", value = "{age: 22, expectIsAdult: true}") 289> public void personIsAdult(int age, boolean expectIsAdult) { ... } 290> ``` 291> 292> This will generate the following test names: 293> 294> ``` 295> MyTest#personIsAdult[teenager] 296> MyTest#personIsAdult[young adult] 297> ``` 298 299### Filtering unwanted parameters 300 301Sometimes, you want to exclude a parameter or a combination of parameters. We 302recommend doing this via JUnit assumptions which is also supported by 303[Truth](https://truth.dev/): 304 305```java 306import static com.google.common.truth.TruthJUnit.assume; 307 308@Test 309public void myTest(@TestParameter Fruit fruit) { 310 assume().that(fruit).isNotEqualTo(Fruit.BANANA); 311 312 // At this point, the test will only run for APPLE and CHERRY. 313 // The BANANA case will silently be ignored. 314} 315 316enum Fruit { APPLE, BANANA, CHERRY } 317``` 318 319Note that the above works regardless of what parameterization framework you 320choose. 321 322## Advanced usage 323 324**Note about JUnit4 vs JUnit5:**<br /> 325The code below assumes you're using JUnit4. For JUnit5 users, simply remove the 326`@RunWith` annotation and replace `@Test` by `@TestParameterInjectorTest`. 327 328### Dynamic parameter generation for `@TestParameter` 329 330Instead of providing a list of parsable strings, you can implement your own 331`TestParameterValuesProvider` as follows: 332 333```java 334import com.google.testing.junit.testparameterinjector.TestParameterValuesProvider; 335 336@Test 337public void matchesAllOf_throwsOnNull( 338 @TestParameter(valuesProvider = CharMatcherProvider.class) CharMatcher charMatcher) { 339 assertThrows(NullPointerException.class, () -> charMatcher.matchesAllOf(null)); 340} 341 342private static final class CharMatcherProvider extends TestParameterValuesProvider { 343 @Override 344 public List<CharMatcher> provideValues(Context context) { 345 return ImmutableList.of(CharMatcher.any(), CharMatcher.ascii(), CharMatcher.whitespace()); 346 } 347} 348``` 349 350Notes: 351 352- The `provideValues()` method can dynamically construct the returned list, 353 e.g. by reading a file. 354- There are no restrictions on the object types returned. 355- The `provideValues()` method is called before `@BeforeClass`, so don't rely 356 on any static state initialized in there. 357- The returned objects' `toString()` will be used for the test names. If you 358 want to customize the value names, you can do that as follows: 359 360 ``` 361 private static final class FruitProvider extends TestParameterValuesProvider { 362 @Override 363 public List<?> provideValues(Context context) { 364 return ImmutableList.of( 365 value(new Apple()).withName("apple"), 366 value(new Banana()).withName("banana")); 367 } 368 } 369 ``` 370 371- The given `Context` contains the test class and other annotations on the 372 `@TestParameter`-annotated parameter/field. This allows more generic 373 providers that take into account custom annotations with extra data, or the 374 implementation of abstract methods on a base test class. 375 376### Dynamic parameter generation for `@TestParameters` 377 378Instead of providing a YAML mapping of parameters, you can implement your own 379`TestParametersValuesProvider` as follows: 380 381```java 382@Test 383@TestParameters(valuesProvider = IsAdultValueProvider.class) 384public void personIsAdult(int age, boolean expectIsAdult) { ... } 385 386static final class IsAdultValueProvider implements TestParametersValuesProvider { 387 @Override public ImmutableList<TestParametersValues> provideValues() { 388 return ImmutableList.of( 389 TestParametersValues.builder() 390 .name("teenager") 391 .addParameter("age", 17) 392 .addParameter("expectIsAdult", false) 393 .build(), 394 TestParametersValues.builder() 395 .name("young adult") 396 .addParameter("age", 22) 397 .addParameter("expectIsAdult", true) 398 .build() 399 ); 400 } 401} 402``` 403