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.18</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.18</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// Durations (segments of number+unit as shown below) 187@TestParameter({"1d", "2h", "3min", "4s", "5ms", "6us", "7ns"}) java.time.Duration d; 188@TestParameter({"1h30min", "-2h10min20s", "1.5h", ".5s", "0"}) java.time.Duration d; 189``` 190 191For non-primitive types (e.g. String, enums, bytes), `"null"` is always parsed as the `null` reference. 192 193#### Multiple parameters: All combinations are run 194 195If there are multiple `@TestParameter`-annotated values applicable to one test 196method, the test is run for all possible combinations of those values. Example: 197 198```java 199@RunWith(TestParameterInjector.class) 200public class MyTest { 201 202 @TestParameter private boolean a; 203 204 @Test public void test1(@TestParameter boolean b, @TestParameter boolean c) { 205 // Run for these combinations: 206 // (a=false, b=false, c=false) 207 // (a=false, b=false, c=true ) 208 // (a=false, b=true, c=false) 209 // (a=false, b=true, c=true ) 210 // (a=true, b=false, c=false) 211 // (a=true, b=false, c=true ) 212 // (a=true, b=true, c=false) 213 // (a=true, b=true, c=true ) 214 } 215} 216``` 217 218If you want to explicitly define which combinations are run, see the next 219sections. 220 221### Use a test enum for enumerating more complex parameter combinations 222 223Use this strategy if you want to: 224 225- Explicitly specify the combination of parameters 226- or your parameters are too large to be encoded in a `String` in a readable 227 way 228 229Example: 230 231```java 232@RunWith(TestParameterInjector.class) 233class MyTest { 234 235 enum FruitVolumeTestCase { 236 APPLE(Fruit.newBuilder().setName("Apple").setShape(SPHERE).build(), /* expectedVolume= */ 3.1), 237 BANANA(Fruit.newBuilder().setName("Banana").setShape(CURVED).build(), /* expectedVolume= */ 2.1), 238 MELON(Fruit.newBuilder().setName("Melon").setShape(SPHERE).build(), /* expectedVolume= */ 6); 239 240 final Fruit fruit; 241 final double expectedVolume; 242 243 FruitVolumeTestCase(Fruit fruit, double expectedVolume) { ... } 244 } 245 246 @Test 247 public void calculateVolume_success(@TestParameter FruitVolumeTestCase fruitVolumeTestCase) { 248 assertThat(calculateVolume(fruitVolumeTestCase.fruit)) 249 .isEqualTo(fruitVolumeTestCase.expectedVolume); 250 } 251} 252``` 253 254The enum constant name has the added benefit of making for sensible test names: 255 256``` 257MyTest#calculateVolume_success[APPLE] 258MyTest#calculateVolume_success[BANANA] 259MyTest#calculateVolume_success[MELON] 260``` 261 262### `@TestParameters` for defining sets of parameters 263 264You can also explicitly enumerate the sets of test parameters via a list of YAML 265mappings: 266 267```java 268@Test 269@TestParameters("{age: 17, expectIsAdult: false}") 270@TestParameters("{age: 22, expectIsAdult: true}") 271public void personIsAdult(int age, boolean expectIsAdult) { ... } 272``` 273 274which would generate the following tests: 275 276``` 277MyTest#personIsAdult[{age: 17, expectIsAdult: false}] 278MyTest#personIsAdult[{age: 22, expectIsAdult: true}] 279``` 280 281The string format supports the same types as `@TestParameter` (e.g. enums). See 282the `@TestParameters` javadoc for more info. 283 284`@TestParameters` works in the same way on the constructor, in which case all 285tests will be run for the given parameter sets. 286 287> Tip: Consider setting a custom name if the YAML string is large: 288> 289> ```java 290> @Test 291> @TestParameters(customName = "teenager", value = "{age: 17, expectIsAdult: false}") 292> @TestParameters(customName = "young adult", value = "{age: 22, expectIsAdult: true}") 293> public void personIsAdult(int age, boolean expectIsAdult) { ... } 294> ``` 295> 296> This will generate the following test names: 297> 298> ``` 299> MyTest#personIsAdult[teenager] 300> MyTest#personIsAdult[young adult] 301> ``` 302 303### Filtering unwanted parameters 304 305Sometimes, you want to exclude a parameter or a combination of parameters. We 306recommend doing this via JUnit assumptions which is also supported by 307[Truth](https://truth.dev/): 308 309```java 310import static com.google.common.truth.TruthJUnit.assume; 311 312@Test 313public void myTest(@TestParameter Fruit fruit) { 314 assume().that(fruit).isNotEqualTo(Fruit.BANANA); 315 316 // At this point, the test will only run for APPLE and CHERRY. 317 // The BANANA case will silently be ignored. 318} 319 320enum Fruit { APPLE, BANANA, CHERRY } 321``` 322 323Note that the above works regardless of what parameterization framework you 324choose. 325 326## Advanced usage 327 328**Note about JUnit4 vs JUnit5:**<br /> 329The code below assumes you're using JUnit4. For JUnit5 users, simply remove the 330`@RunWith` annotation and replace `@Test` by `@TestParameterInjectorTest`. 331 332### Dynamic parameter generation for `@TestParameter` 333 334Instead of providing a list of parsable strings, you can implement your own 335`TestParameterValuesProvider` as follows: 336 337```java 338import com.google.testing.junit.testparameterinjector.TestParameterValuesProvider; 339 340@Test 341public void matchesAllOf_throwsOnNull( 342 @TestParameter(valuesProvider = CharMatcherProvider.class) CharMatcher charMatcher) { 343 assertThrows(NullPointerException.class, () -> charMatcher.matchesAllOf(null)); 344} 345 346private static final class CharMatcherProvider extends TestParameterValuesProvider { 347 @Override 348 public List<CharMatcher> provideValues(Context context) { 349 return ImmutableList.of(CharMatcher.any(), CharMatcher.ascii(), CharMatcher.whitespace()); 350 } 351} 352``` 353 354Notes: 355 356- The `provideValues()` method can dynamically construct the returned list, 357 e.g. by reading a file. 358- There are no restrictions on the object types returned. 359- The `provideValues()` method is called before `@BeforeClass`, so don't rely 360 on any static state initialized in there. 361- The returned objects' `toString()` will be used for the test names. If you 362 want to customize the value names, you can do that as follows: 363 364 ``` 365 private static final class FruitProvider extends TestParameterValuesProvider { 366 @Override 367 public List<?> provideValues(Context context) { 368 return ImmutableList.of( 369 value(new Apple()).withName("apple"), 370 value(new Banana()).withName("banana")); 371 } 372 } 373 ``` 374 375- The given `Context` contains the test class and other annotations on the 376 `@TestParameter`-annotated parameter/field. This allows more generic 377 providers that take into account custom annotations with extra data, or the 378 implementation of abstract methods on a base test class. 379 380### Dynamic parameter generation for `@TestParameters` 381 382Instead of providing a YAML mapping of parameters, you can implement your own 383`TestParametersValuesProvider` as follows: 384 385```java 386import com.google.testing.junit.testparameterinjector.TestParametersValuesProvider; 387import com.google.testing.junit.testparameterinjector.TestParameters.TestParametersValues; 388 389@Test 390@TestParameters(valuesProvider = IsAdultValueProvider.class) 391public void personIsAdult(int age, boolean expectIsAdult) { ... } 392 393static final class IsAdultValueProvider extends TestParametersValuesProvider { 394 @Override public ImmutableList<TestParametersValues> provideValues(Context context) { 395 return ImmutableList.of( 396 TestParametersValues.builder() 397 .name("teenager") 398 .addParameter("age", 17) 399 .addParameter("expectIsAdult", false) 400 .build(), 401 TestParametersValues.builder() 402 .name("young adult") 403 .addParameter("age", 22) 404 .addParameter("expectIsAdult", true) 405 .build() 406 ); 407 } 408} 409``` 410