0
# Parameterized Tests
1
2
Advanced parameterized testing capabilities that allow running the same test logic with different sets of arguments. JUnit Jupiter provides multiple ways to supply test arguments with support for custom conversion and aggregation.
3
4
## Imports
5
6
```java
7
import org.junit.jupiter.params.ParameterizedTest;
8
import org.junit.jupiter.params.provider.*;
9
import org.junit.jupiter.params.aggregator.*;
10
import org.junit.jupiter.params.converter.*;
11
import static org.junit.jupiter.api.Assertions.*;
12
```
13
14
## Capabilities
15
16
### Parameterized Test Annotation
17
18
Core annotation for defining parameterized tests.
19
20
```java { .api }
21
/**
22
* Marks a method as a parameterized test with multiple argument sources
23
*/
24
@Target(ElementType.METHOD)
25
@Retention(RetentionPolicy.RUNTIME)
26
@interface ParameterizedTest {
27
/**
28
* Custom name pattern for parameterized test invocations
29
*/
30
String name() default "[{index}] {arguments}";
31
32
/**
33
* How to handle argument count mismatches
34
*/
35
ArgumentCountValidationMode argumentCountValidationMode() default ArgumentCountValidationMode.STRICT;
36
}
37
38
enum ArgumentCountValidationMode {
39
STRICT, // Fail if parameter count doesn't match
40
LENIENT, // Allow missing parameters (null values)
41
IGNORE // Ignore extra arguments
42
}
43
```
44
45
**Basic Usage:**
46
47
```java
48
@ParameterizedTest
49
@ValueSource(ints = {1, 2, 3})
50
void testWithValueSource(int argument) {
51
assertTrue(argument > 0);
52
}
53
54
@ParameterizedTest(name = "Run {index}: testing with value {0}")
55
@ValueSource(strings = {"apple", "banana", "cherry"})
56
void testWithCustomName(String fruit) {
57
assertNotNull(fruit);
58
assertTrue(fruit.length() > 3);
59
}
60
```
61
62
### Value Sources
63
64
Simple argument sources for primitive types and strings.
65
66
```java { .api }
67
/**
68
* Array of literal values as arguments
69
*/
70
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
71
@Retention(RetentionPolicy.RUNTIME)
72
@ArgumentsSource(ValueArgumentsProvider.class)
73
@interface ValueSource {
74
short[] shorts() default {};
75
byte[] bytes() default {};
76
int[] ints() default {};
77
long[] longs() default {};
78
float[] floats() default {};
79
double[] doubles() default {};
80
char[] chars() default {};
81
boolean[] booleans() default {};
82
String[] strings() default {};
83
Class<?>[] classes() default {};
84
}
85
86
/**
87
* Container for multiple value sources
88
*/
89
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
90
@Retention(RetentionPolicy.RUNTIME)
91
@interface ValueSources {
92
ValueSource[] value();
93
}
94
```
95
96
**Usage Examples:**
97
98
```java
99
@ParameterizedTest
100
@ValueSource(ints = {1, 2, 3, 4, 5})
101
void testNumbers(int number) {
102
assertTrue(number > 0 && number < 6);
103
}
104
105
@ParameterizedTest
106
@ValueSource(strings = {"", " "})
107
void testBlankStrings(String input) {
108
assertTrue(input.isBlank());
109
}
110
111
@ParameterizedTest
112
@ValueSource(booleans = {true, false})
113
void testBooleans(boolean value) {
114
// Test both true and false cases
115
assertNotNull(Boolean.valueOf(value));
116
}
117
118
@ParameterizedTest
119
@ValueSource(classes = {String.class, Integer.class, List.class})
120
void testClasses(Class<?> clazz) {
121
assertNotNull(clazz);
122
assertNotNull(clazz.getName());
123
}
124
```
125
126
### Null and Empty Sources
127
128
Special argument sources for null and empty values.
129
130
```java { .api }
131
/**
132
* Provides a single null argument
133
*/
134
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
135
@Retention(RetentionPolicy.RUNTIME)
136
@ArgumentsSource(NullArgumentsProvider.class)
137
@interface NullSource {
138
}
139
140
/**
141
* Provides empty values for strings, lists, sets, maps, and primitive arrays
142
*/
143
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
144
@Retention(RetentionPolicy.RUNTIME)
145
@ArgumentsSource(EmptyArgumentsProvider.class)
146
@interface EmptySource {
147
}
148
149
/**
150
* Combines null and empty sources
151
*/
152
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
153
@Retention(RetentionPolicy.RUNTIME)
154
@NullSource
155
@EmptySource
156
@interface NullAndEmptySource {
157
}
158
```
159
160
**Usage Examples:**
161
162
```java
163
@ParameterizedTest
164
@NullSource
165
@ValueSource(strings = {"", " ", "valid"})
166
void testStringValidation(String input) {
167
// Test with null, empty, blank, and valid strings
168
String result = StringUtils.clean(input);
169
// Assert based on input type
170
}
171
172
@ParameterizedTest
173
@NullAndEmptySource
174
@ValueSource(strings = {"apple", "banana"})
175
void testStringProcessing(String input) {
176
// Test null, empty, and actual values
177
String processed = processString(input);
178
if (input == null || input.isEmpty()) {
179
assertEquals("default", processed);
180
} else {
181
assertNotEquals("default", processed);
182
}
183
}
184
185
@ParameterizedTest
186
@EmptySource
187
@ValueSource(ints = {1, 2, 3})
188
void testIntArrays(int[] array) {
189
// Test with empty array and arrays with values
190
assertNotNull(array);
191
}
192
```
193
194
### Enum Sources
195
196
Arguments from enum values with filtering options.
197
198
```java { .api }
199
/**
200
* Provides enum values as arguments
201
*/
202
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
203
@Retention(RetentionPolicy.RUNTIME)
204
@ArgumentsSource(EnumArgumentsProvider.class)
205
@interface EnumSource {
206
/**
207
* Enum class to get values from
208
*/
209
Class<? extends Enum<?>> value();
210
211
/**
212
* Enum constant names to include/exclude
213
*/
214
String[] names() default {};
215
216
/**
217
* Whether to include or exclude specified names
218
*/
219
Mode mode() default Mode.INCLUDE;
220
221
enum Mode {
222
INCLUDE, // Include only specified names
223
EXCLUDE, // Exclude specified names
224
MATCH_ALL, // Include names matching all patterns
225
MATCH_ANY // Include names matching any pattern
226
}
227
}
228
229
/**
230
* Container for multiple enum sources
231
*/
232
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
233
@Retention(RetentionPolicy.RUNTIME)
234
@interface EnumSources {
235
EnumSource[] value();
236
}
237
```
238
239
**Usage Examples:**
240
241
```java
242
enum Color {
243
RED, GREEN, BLUE, YELLOW, PURPLE
244
}
245
246
@ParameterizedTest
247
@EnumSource(Color.class)
248
void testAllColors(Color color) {
249
assertNotNull(color);
250
assertTrue(color.name().length() > 2);
251
}
252
253
@ParameterizedTest
254
@EnumSource(value = Color.class, names = {"RED", "BLUE"})
255
void testSpecificColors(Color color) {
256
assertTrue(color == Color.RED || color == Color.BLUE);
257
}
258
259
@ParameterizedTest
260
@EnumSource(value = Color.class, names = {"YELLOW"}, mode = EnumSource.Mode.EXCLUDE)
261
void testAllColorsExceptYellow(Color color) {
262
assertNotEquals(Color.YELLOW, color);
263
}
264
265
@ParameterizedTest
266
@EnumSource(value = Color.class, names = {"^B.*"}, mode = EnumSource.Mode.MATCH_ALL)
267
void testColorsStartingWithB(Color color) {
268
assertTrue(color.name().startsWith("B"));
269
}
270
```
271
272
### CSV Sources
273
274
Arguments from CSV data, either inline or from files.
275
276
```java { .api }
277
/**
278
* Provides CSV data as arguments
279
*/
280
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
281
@Retention(RetentionPolicy.RUNTIME)
282
@ArgumentsSource(CsvArgumentsProvider.class)
283
@interface CsvSource {
284
/**
285
* CSV records as string array
286
*/
287
String[] value();
288
289
/**
290
* Column delimiter character
291
*/
292
char delimiter() default ',';
293
294
/**
295
* String to represent null values
296
*/
297
String nullValues() default "";
298
299
/**
300
* Quote character for escaping
301
*/
302
char quoteCharacter() default '"';
303
304
/**
305
* How to handle empty values
306
*/
307
EmptyValue emptyValue() default EmptyValue.EMPTY_STRING;
308
309
/**
310
* Whether to ignore leading/trailing whitespace
311
*/
312
boolean ignoreLeadingAndTrailingWhitespace() default true;
313
314
enum EmptyValue {
315
EMPTY_STRING, // Empty string ""
316
NULL_REFERENCE // null
317
}
318
}
319
320
/**
321
* Container for multiple CSV sources
322
*/
323
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
324
@Retention(RetentionPolicy.RUNTIME)
325
@interface CsvSources {
326
CsvSource[] value();
327
}
328
```
329
330
**Usage Examples:**
331
332
```java
333
@ParameterizedTest
334
@CsvSource({
335
"apple, 1",
336
"banana, 2",
337
"'lemon, lime', 3"
338
})
339
void testWithCsvSource(String fruit, int rank) {
340
assertNotNull(fruit);
341
assertTrue(rank > 0);
342
}
343
344
@ParameterizedTest
345
@CsvSource(value = {
346
"John:25:Engineer",
347
"Jane:30:Manager",
348
"Bob:35:Developer"
349
}, delimiter = ':')
350
void testPersonData(String name, int age, String role) {
351
assertNotNull(name);
352
assertTrue(age > 0);
353
assertNotNull(role);
354
}
355
356
@ParameterizedTest
357
@CsvSource(value = {
358
"test, NULL, 42",
359
"example, , 0"
360
}, nullValues = "NULL")
361
void testWithNullValues(String str, String nullableStr, int number) {
362
assertNotNull(str);
363
// nullableStr might be null
364
assertTrue(number >= 0);
365
}
366
```
367
368
### CSV File Sources
369
370
Arguments from external CSV files.
371
372
```java { .api }
373
/**
374
* Provides CSV data from files as arguments
375
*/
376
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
377
@Retention(RetentionPolicy.RUNTIME)
378
@ArgumentsSource(CsvFileArgumentsProvider.class)
379
@interface CsvFileSource {
380
/**
381
* CSV file resources (classpath relative)
382
*/
383
String[] resources() default {};
384
385
/**
386
* CSV files (file system paths)
387
*/
388
String[] files() default {};
389
390
/**
391
* Character encoding for files
392
*/
393
String encoding() default "UTF-8";
394
395
/**
396
* Line separator for files
397
*/
398
String lineSeparator() default "\n";
399
400
/**
401
* Column delimiter character
402
*/
403
char delimiter() default ',';
404
405
/**
406
* String to represent null values
407
*/
408
String nullValues() default "";
409
410
/**
411
* Quote character for escaping
412
*/
413
char quoteCharacter() default '"';
414
415
/**
416
* How to handle empty values
417
*/
418
CsvSource.EmptyValue emptyValue() default CsvSource.EmptyValue.EMPTY_STRING;
419
420
/**
421
* Whether to ignore leading/trailing whitespace
422
*/
423
boolean ignoreLeadingAndTrailingWhitespace() default true;
424
425
/**
426
* Number of header lines to skip
427
*/
428
int numLinesToSkip() default 0;
429
}
430
431
/**
432
* Container for multiple CSV file sources
433
*/
434
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
435
@Retention(RetentionPolicy.RUNTIME)
436
@interface CsvFileSources {
437
CsvFileSource[] value();
438
}
439
```
440
441
**Usage Examples:**
442
443
```java
444
@ParameterizedTest
445
@CsvFileSource(resources = "/test-data.csv", numLinesToSkip = 1)
446
void testWithCsvFileSource(String name, int age, String city) {
447
assertNotNull(name);
448
assertTrue(age > 0);
449
assertNotNull(city);
450
}
451
452
@ParameterizedTest
453
@CsvFileSource(files = "src/test/resources/users.csv", delimiter = ';')
454
void testUserData(String username, String email, boolean active) {
455
assertNotNull(username);
456
assertTrue(email.contains("@"));
457
// active can be true or false
458
}
459
```
460
461
### Method Sources
462
463
Arguments from static methods.
464
465
```java { .api }
466
/**
467
* Provides arguments from static methods
468
*/
469
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
470
@Retention(RetentionPolicy.RUNTIME)
471
@ArgumentsSource(MethodArgumentsProvider.class)
472
@interface MethodSource {
473
/**
474
* Method names that provide arguments
475
* If empty, uses test method name
476
*/
477
String[] value() default {};
478
}
479
480
/**
481
* Container for multiple method sources
482
*/
483
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
484
@Retention(RetentionPolicy.RUNTIME)
485
@interface MethodSources {
486
MethodSource[] value();
487
}
488
```
489
490
**Usage Examples:**
491
492
```java
493
@ParameterizedTest
494
@MethodSource("stringProvider")
495
void testWithMethodSource(String argument) {
496
assertNotNull(argument);
497
}
498
499
static Stream<String> stringProvider() {
500
return Stream.of("apple", "banana", "cherry");
501
}
502
503
@ParameterizedTest
504
@MethodSource("personProvider")
505
void testPersons(Person person) {
506
assertNotNull(person.getName());
507
assertTrue(person.getAge() > 0);
508
}
509
510
static Stream<Person> personProvider() {
511
return Stream.of(
512
new Person("John", 25),
513
new Person("Jane", 30),
514
new Person("Bob", 35)
515
);
516
}
517
518
@ParameterizedTest
519
@MethodSource("argumentProvider")
520
void testWithMultipleArguments(int number, String text, boolean flag) {
521
assertTrue(number > 0);
522
assertNotNull(text);
523
// flag can be any boolean value
524
}
525
526
static Stream<Arguments> argumentProvider() {
527
return Stream.of(
528
Arguments.of(1, "first", true),
529
Arguments.of(2, "second", false),
530
Arguments.of(3, "third", true)
531
);
532
}
533
```
534
535
### Field Sources
536
537
Arguments from static fields.
538
539
```java { .api }
540
/**
541
* Provides arguments from static fields
542
*/
543
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
544
@Retention(RetentionPolicy.RUNTIME)
545
@ArgumentsSource(FieldArgumentsProvider.class)
546
@interface FieldSource {
547
/**
548
* Field names that provide arguments
549
* If empty, uses test method name
550
*/
551
String[] value() default {};
552
}
553
554
/**
555
* Container for multiple field sources
556
*/
557
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
558
@Retention(RetentionPolicy.RUNTIME)
559
@interface FieldSources {
560
FieldSource[] value();
561
}
562
```
563
564
**Usage Examples:**
565
566
```java
567
static List<String> fruits = Arrays.asList("apple", "banana", "cherry");
568
569
@ParameterizedTest
570
@FieldSource("fruits")
571
void testWithFieldSource(String fruit) {
572
assertNotNull(fruit);
573
assertTrue(fruit.length() > 3);
574
}
575
576
static Stream<Arguments> testData = Stream.of(
577
Arguments.of(1, "one"),
578
Arguments.of(2, "two"),
579
Arguments.of(3, "three")
580
);
581
582
@ParameterizedTest
583
@FieldSource("testData")
584
void testWithArgumentsField(int number, String word) {
585
assertTrue(number > 0);
586
assertNotNull(word);
587
}
588
```
589
590
### Custom Argument Sources
591
592
Create custom argument providers for complex scenarios.
593
594
```java { .api }
595
/**
596
* Custom arguments source annotation
597
*/
598
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
599
@Retention(RetentionPolicy.RUNTIME)
600
@ArgumentsSource(CustomArgumentsProvider.class)
601
@interface ArgumentsSource {
602
/**
603
* ArgumentsProvider implementation class
604
*/
605
Class<? extends ArgumentsProvider> value();
606
}
607
608
/**
609
* Container for multiple custom sources
610
*/
611
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
612
@Retention(RetentionPolicy.RUNTIME)
613
@interface ArgumentsSources {
614
ArgumentsSource[] value();
615
}
616
617
/**
618
* Arguments provider interface
619
*/
620
interface ArgumentsProvider {
621
/**
622
* Provide arguments for parameterized test
623
*/
624
Stream<? extends Arguments> provideArguments(ExtensionContext context) throws Exception;
625
}
626
627
/**
628
* Base class for annotation-based providers
629
*/
630
abstract class AnnotationBasedArgumentsProvider<T extends Annotation> implements ArgumentsProvider {
631
/**
632
* Accept annotation for configuration
633
*/
634
protected abstract void accept(T annotation);
635
}
636
```
637
638
**Usage Example:**
639
640
```java
641
@Target(ElementType.METHOD)
642
@Retention(RetentionPolicy.RUNTIME)
643
@ArgumentsSource(RandomIntegerProvider.class)
644
@interface RandomIntegers {
645
int count() default 10;
646
int min() default 0;
647
int max() default 100;
648
}
649
650
class RandomIntegerProvider extends AnnotationBasedArgumentsProvider<RandomIntegers> {
651
private int count;
652
private int min;
653
private int max;
654
655
@Override
656
protected void accept(RandomIntegers annotation) {
657
this.count = annotation.count();
658
this.min = annotation.min();
659
this.max = annotation.max();
660
}
661
662
@Override
663
public Stream<Arguments> provideArguments(ExtensionContext context) {
664
Random random = new Random();
665
return random.ints(count, min, max)
666
.mapToObj(Arguments::of);
667
}
668
}
669
670
@ParameterizedTest
671
@RandomIntegers(count = 5, min = 1, max = 10)
672
void testWithRandomIntegers(int value) {
673
assertTrue(value >= 1 && value <= 10);
674
}
675
```
676
677
### Argument Conversion
678
679
Convert string arguments to other types automatically or with custom converters.
680
681
```java { .api }
682
/**
683
* Custom argument converter annotation
684
*/
685
@Target({ElementType.ANNOTATION_TYPE, ElementType.PARAMETER})
686
@Retention(RetentionPolicy.RUNTIME)
687
@interface ConvertWith {
688
/**
689
* ArgumentConverter implementation class
690
*/
691
Class<? extends ArgumentConverter> value();
692
}
693
694
/**
695
* Java time conversion pattern
696
*/
697
@Target({ElementType.ANNOTATION_TYPE, ElementType.PARAMETER})
698
@Retention(RetentionPolicy.RUNTIME)
699
@ConvertWith(JavaTimeArgumentConverter.class)
700
@interface JavaTimeConversionPattern {
701
/**
702
* Pattern for parsing date/time
703
*/
704
String value();
705
}
706
707
/**
708
* Argument converter interface
709
*/
710
interface ArgumentConverter<S, T> {
711
/**
712
* Convert source argument to target type
713
*/
714
T convert(S source, ParameterContext context) throws ArgumentConversionException;
715
}
716
717
/**
718
* Simple converter for single argument types
719
*/
720
abstract class SimpleArgumentConverter<S, T> implements ArgumentConverter<S, T> {
721
@Override
722
public final T convert(S source, ParameterContext context) throws ArgumentConversionException {
723
return convert(source, context.getParameter().getType());
724
}
725
726
/**
727
* Convert source to target type
728
*/
729
protected abstract T convert(S source, Class<?> targetType) throws ArgumentConversionException;
730
}
731
732
/**
733
* Typed converter with type safety
734
*/
735
abstract class TypedArgumentConverter<S, T> extends SimpleArgumentConverter<S, T> {
736
private final Class<S> sourceType;
737
private final Class<T> targetType;
738
739
protected TypedArgumentConverter(Class<S> sourceType, Class<T> targetType) {
740
this.sourceType = sourceType;
741
this.targetType = targetType;
742
}
743
}
744
```
745
746
**Usage Examples:**
747
748
```java
749
@ParameterizedTest
750
@ValueSource(strings = {"2023-01-01", "2023-12-31"})
751
void testDates(@JavaTimeConversionPattern("yyyy-MM-dd") LocalDate date) {
752
assertNotNull(date);
753
assertEquals(2023, date.getYear());
754
}
755
756
class StringToPersonConverter extends TypedArgumentConverter<String, Person> {
757
protected StringToPersonConverter() {
758
super(String.class, Person.class);
759
}
760
761
@Override
762
protected Person convert(String source, Class<?> targetType) {
763
String[] parts = source.split(",");
764
return new Person(parts[0], Integer.parseInt(parts[1]));
765
}
766
}
767
768
@ParameterizedTest
769
@ValueSource(strings = {"John,25", "Jane,30", "Bob,35"})
770
void testPersonConversion(@ConvertWith(StringToPersonConverter.class) Person person) {
771
assertNotNull(person.getName());
772
assertTrue(person.getAge() > 0);
773
}
774
```
775
776
### Argument Aggregation
777
778
Aggregate multiple arguments into complex objects.
779
780
```java { .api }
781
/**
782
* Custom argument aggregator annotation
783
*/
784
@Target(ElementType.PARAMETER)
785
@Retention(RetentionPolicy.RUNTIME)
786
@interface AggregateWith {
787
/**
788
* ArgumentsAggregator implementation class
789
*/
790
Class<? extends ArgumentsAggregator> value();
791
}
792
793
/**
794
* Arguments aggregator interface
795
*/
796
interface ArgumentsAggregator {
797
/**
798
* Aggregate arguments into single object
799
*/
800
Object aggregateArguments(ArgumentsAccessor accessor, ParameterContext context)
801
throws ArgumentsAggregationException;
802
}
803
804
/**
805
* Arguments accessor for retrieving individual arguments
806
*/
807
interface ArgumentsAccessor {
808
Object get(int index);
809
<T> T get(int index, Class<T> requiredType);
810
Character getCharacter(int index);
811
Boolean getBoolean(int index);
812
Byte getByte(int index);
813
Short getShort(int index);
814
Integer getInteger(int index);
815
Long getLong(int index);
816
Float getFloat(int index);
817
Double getDouble(int index);
818
String getString(int index);
819
int size();
820
Object[] toArray();
821
List<Object> toList();
822
}
823
```
824
825
**Usage Examples:**
826
827
```java
828
class PersonAggregator implements ArgumentsAggregator {
829
@Override
830
public Object aggregateArguments(ArgumentsAccessor accessor, ParameterContext context) {
831
return new Person(accessor.getString(0), accessor.getInteger(1));
832
}
833
}
834
835
@ParameterizedTest
836
@CsvSource({
837
"John, 25",
838
"Jane, 30",
839
"Bob, 35"
840
})
841
void testPersonAggregation(@AggregateWith(PersonAggregator.class) Person person) {
842
assertNotNull(person.getName());
843
assertTrue(person.getAge() > 0);
844
}
845
846
@ParameterizedTest
847
@CsvSource({
848
"John, 25, Engineer",
849
"Jane, 30, Manager",
850
"Bob, 35, Developer"
851
})
852
void testWithArgumentsAccessor(ArgumentsAccessor arguments) {
853
String name = arguments.getString(0);
854
int age = arguments.getInteger(1);
855
String role = arguments.getString(2);
856
857
Person person = new Person(name, age, role);
858
assertNotNull(person);
859
}
860
```
861
862
### Arguments Utility
863
864
Utility class for creating argument sets programmatically.
865
866
```java { .api }
867
/**
868
* Factory for creating Arguments instances
869
*/
870
interface Arguments {
871
/**
872
* Create Arguments from array of objects
873
*/
874
static Arguments of(Object... arguments);
875
876
/**
877
* Get arguments as object array
878
*/
879
Object[] get();
880
}
881
```
882
883
**Usage Example:**
884
885
```java
886
static Stream<Arguments> complexArgumentProvider() {
887
return Stream.of(
888
Arguments.of(1, "apple", true, new Person("John", 25)),
889
Arguments.of(2, "banana", false, new Person("Jane", 30)),
890
Arguments.of(3, "cherry", true, new Person("Bob", 35))
891
);
892
}
893
894
@ParameterizedTest
895
@MethodSource("complexArgumentProvider")
896
void testComplexArguments(int id, String fruit, boolean active, Person person) {
897
assertTrue(id > 0);
898
assertNotNull(fruit);
899
assertNotNull(person);
900
}
901
```