0
# Custom Sources
1
2
Extension points for custom argument providers and the fundamental interfaces that power all argument sources.
3
4
## Capabilities
5
6
### @ArgumentsSource Annotation
7
8
Registers custom ArgumentsProvider implementations for specialized test data generation.
9
10
```java { .api }
11
/**
12
* Registers a custom ArgumentsProvider implementation
13
*/
14
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
15
@Retention(RetentionPolicy.RUNTIME)
16
@Documented
17
@API(status = STABLE, since = "5.0")
18
@Repeatable(ArgumentsSources.class)
19
@interface ArgumentsSource {
20
/**
21
* ArgumentsProvider implementation class
22
*/
23
Class<? extends ArgumentsProvider> value();
24
}
25
```
26
27
### ArgumentsProvider Interface
28
29
Core contract for providing streams of Arguments to parameterized tests.
30
31
```java { .api }
32
/**
33
* Contract for providing streams of Arguments to parameterized tests
34
* Implementations must be thread-safe and have a no-args constructor
35
* or a single constructor whose parameters can be resolved by JUnit
36
*/
37
@API(status = STABLE, since = "5.0")
38
@FunctionalInterface
39
interface ArgumentsProvider {
40
/**
41
* Provides a stream of Arguments for test invocations
42
*
43
* @param context the current extension context
44
* @return stream of Arguments, never null
45
* @throws Exception if argument generation fails
46
*/
47
Stream<? extends Arguments> provideArguments(ExtensionContext context)
48
throws Exception;
49
}
50
```
51
52
### Arguments Interface
53
54
Represents a set of arguments for one parameterized test invocation.
55
56
```java { .api }
57
/**
58
* Represents a set of arguments for one parameterized test invocation
59
*/
60
@API(status = STABLE, since = "5.0")
61
interface Arguments {
62
/**
63
* Returns the arguments as an Object array
64
*/
65
Object[] get();
66
67
/**
68
* Factory method for creating Arguments from objects
69
*/
70
static Arguments of(Object... arguments) {
71
return () -> arguments;
72
}
73
74
/**
75
* Alias for of() method
76
*/
77
static Arguments arguments(Object... arguments) {
78
return of(arguments);
79
}
80
81
/**
82
* Creates a named argument set (experimental)
83
*/
84
@API(status = EXPERIMENTAL, since = "5.12")
85
static ArgumentSet argumentSet(String name, Object... arguments) {
86
return new ArgumentSet(name, arguments);
87
}
88
89
/**
90
* Named argument set with display name (experimental)
91
*/
92
@API(status = EXPERIMENTAL, since = "5.12")
93
class ArgumentSet implements Arguments {
94
private final String name;
95
private final Object[] arguments;
96
97
ArgumentSet(String name, Object[] arguments) {
98
this.name = name;
99
this.arguments = arguments;
100
}
101
102
/**
103
* Returns the argument set name
104
*/
105
public String getName() {
106
return name;
107
}
108
109
@Override
110
public Object[] get() {
111
return arguments;
112
}
113
}
114
}
115
```
116
117
**Usage Examples:**
118
119
```java
120
import org.junit.jupiter.params.ParameterizedTest;
121
import org.junit.jupiter.params.provider.ArgumentsSource;
122
import org.junit.jupiter.params.provider.Arguments;
123
import org.junit.jupiter.params.provider.ArgumentsProvider;
124
import org.junit.jupiter.api.extension.ExtensionContext;
125
import java.lang.annotation.*;
126
import java.util.stream.Stream;
127
import java.time.*;
128
129
// Custom annotation with ArgumentsProvider
130
@Target(ElementType.METHOD)
131
@Retention(RetentionPolicy.RUNTIME)
132
@ArgumentsSource(DateRangeProvider.class)
133
@interface DateRange {
134
String start();
135
String end();
136
int stepDays() default 1;
137
}
138
139
// Custom ArgumentsProvider implementation
140
class DateRangeProvider implements ArgumentsProvider {
141
142
@Override
143
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
144
DateRange dateRange = context.getRequiredTestMethod()
145
.getAnnotation(DateRange.class);
146
147
LocalDate start = LocalDate.parse(dateRange.start());
148
LocalDate end = LocalDate.parse(dateRange.end());
149
int stepDays = dateRange.stepDays();
150
151
return start.datesUntil(end.plusDays(1), Period.ofDays(stepDays))
152
.map(Arguments::of);
153
}
154
}
155
156
class CustomSourceExamples {
157
158
// Using custom date range provider
159
@ParameterizedTest
160
@DateRange(start = "2023-01-01", end = "2023-01-05")
161
void testDateRange(LocalDate date) {
162
assertNotNull(date);
163
assertTrue(date.getYear() == 2023);
164
assertTrue(date.getMonthValue() == 1);
165
}
166
167
// Using custom provider with step
168
@ParameterizedTest
169
@DateRange(start = "2023-01-01", end = "2023-01-10", stepDays = 3)
170
void testDateRangeWithStep(LocalDate date) {
171
assertNotNull(date);
172
// Tests: 2023-01-01, 2023-01-04, 2023-01-07, 2023-01-10
173
}
174
175
// Direct ArgumentsSource usage
176
@ParameterizedTest
177
@ArgumentsSource(RandomNumberProvider.class)
178
void testWithRandomNumbers(int number) {
179
assertTrue(number >= 1 && number <= 100);
180
}
181
}
182
183
// Another custom provider example
184
class RandomNumberProvider implements ArgumentsProvider {
185
186
@Override
187
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
188
return new Random(42) // Fixed seed for reproducible tests
189
.ints(5, 1, 101) // 5 random integers between 1-100
190
.mapToObj(Arguments::of);
191
}
192
}
193
```
194
195
### Advanced Custom Provider Patterns
196
197
**Database-driven ArgumentsProvider:**
198
199
```java
200
import javax.sql.DataSource;
201
import java.sql.*;
202
203
// Custom annotation for database queries
204
@Target(ElementType.METHOD)
205
@Retention(RetentionPolicy.RUNTIME)
206
@ArgumentsSource(DatabaseProvider.class)
207
@interface DatabaseQuery {
208
String sql();
209
String dataSource() default "default";
210
}
211
212
class DatabaseProvider implements ArgumentsProvider {
213
214
@Override
215
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
216
DatabaseQuery query = context.getRequiredTestMethod()
217
.getAnnotation(DatabaseQuery.class);
218
219
try {
220
DataSource dataSource = getDataSource(query.dataSource());
221
return executeQuery(dataSource, query.sql());
222
} catch (SQLException e) {
223
throw new RuntimeException("Failed to execute database query", e);
224
}
225
}
226
227
private Stream<Arguments> executeQuery(DataSource dataSource, String sql)
228
throws SQLException {
229
// Implementation to execute SQL and return Arguments stream
230
// This is a simplified example
231
return Stream.empty();
232
}
233
234
private DataSource getDataSource(String name) {
235
// Implementation to get DataSource by name
236
return null;
237
}
238
}
239
240
class DatabaseTestExample {
241
242
@ParameterizedTest
243
@DatabaseQuery(sql = "SELECT id, name, price FROM products WHERE active = true")
244
void testActiveProducts(int id, String name, double price) {
245
assertTrue(id > 0);
246
assertNotNull(name);
247
assertTrue(price >= 0);
248
}
249
}
250
```
251
252
**Configuration-driven ArgumentsProvider:**
253
254
```java
255
import java.util.Properties;
256
import java.io.InputStream;
257
258
@Target(ElementType.METHOD)
259
@Retention(RetentionPolicy.RUNTIME)
260
@ArgumentsSource(ConfigurationProvider.class)
261
@interface ConfigurationSource {
262
String file();
263
String prefix() default "";
264
}
265
266
class ConfigurationProvider implements ArgumentsProvider {
267
268
@Override
269
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
270
ConfigurationSource config = context.getRequiredTestMethod()
271
.getAnnotation(ConfigurationSource.class);
272
273
try {
274
Properties props = loadProperties(config.file());
275
String prefix = config.prefix();
276
277
return props.entrySet().stream()
278
.filter(entry -> entry.getKey().toString().startsWith(prefix))
279
.map(entry -> Arguments.of(
280
entry.getKey().toString(),
281
entry.getValue().toString()
282
));
283
} catch (Exception e) {
284
throw new RuntimeException("Failed to load configuration", e);
285
}
286
}
287
288
private Properties loadProperties(String filename) throws Exception {
289
Properties props = new Properties();
290
try (InputStream input = getClass().getResourceAsStream(filename)) {
291
props.load(input);
292
}
293
return props;
294
}
295
}
296
297
class ConfigurationTestExample {
298
299
@ParameterizedTest
300
@ConfigurationSource(file = "/test.properties", prefix = "api.")
301
void testApiConfiguration(String key, String value) {
302
assertTrue(key.startsWith("api."));
303
assertNotNull(value);
304
assertFalse(value.trim().isEmpty());
305
}
306
}
307
```
308
309
### AnnotationConsumer Support
310
311
Custom providers can consume configuration from annotations using the AnnotationConsumer interface.
312
313
```java { .api }
314
/**
315
* Functional interface for consuming configuration annotations
316
*/
317
@API(status = STABLE, since = "5.0")
318
@FunctionalInterface
319
interface AnnotationConsumer<A extends Annotation> extends Consumer<A> {
320
// Inherits accept(A annotation) method from Consumer
321
}
322
```
323
324
**Example with AnnotationConsumer:**
325
326
```java
327
import org.junit.jupiter.params.support.AnnotationConsumer;
328
329
@Target(ElementType.METHOD)
330
@Retention(RetentionPolicy.RUNTIME)
331
@ArgumentsSource(ConfigurableRangeProvider.class)
332
@interface NumberRange {
333
int min() default 1;
334
int max() default 10;
335
int count() default 5;
336
}
337
338
class ConfigurableRangeProvider implements ArgumentsProvider,
339
AnnotationConsumer<NumberRange> {
340
341
private NumberRange range;
342
343
@Override
344
public void accept(NumberRange numberRange) {
345
this.range = numberRange;
346
}
347
348
@Override
349
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
350
Random random = new Random(42);
351
return random.ints(range.count(), range.min(), range.max() + 1)
352
.mapToObj(Arguments::of);
353
}
354
}
355
356
class AnnotationConsumerExample {
357
358
@ParameterizedTest
359
@NumberRange(min = 10, max = 20, count = 3)
360
void testWithConfigurableRange(int number) {
361
assertTrue(number >= 10 && number <= 20);
362
}
363
}
364
```
365
366
### Container Annotation
367
368
Container annotation for multiple @ArgumentsSource annotations.
369
370
```java { .api }
371
/**
372
* Container annotation for multiple @ArgumentsSource annotations
373
*/
374
@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD})
375
@Retention(RetentionPolicy.RUNTIME)
376
@Documented
377
@API(status = STABLE, since = "5.0")
378
@interface ArgumentsSources {
379
ArgumentsSource[] value();
380
}
381
```
382
383
**Usage Example:**
384
385
```java
386
class MultipleArgumentsSourceExample {
387
388
@ParameterizedTest
389
@ArgumentsSource(RandomNumberProvider.class)
390
@ArgumentsSource(SequentialNumberProvider.class)
391
void testWithMultipleSources(int number) {
392
assertTrue(number > 0);
393
// Tests with both random and sequential numbers
394
}
395
}
396
397
class SequentialNumberProvider implements ArgumentsProvider {
398
@Override
399
public Stream<? extends Arguments> provideArguments(ExtensionContext context) {
400
return Stream.of(1, 2, 3, 4, 5).map(Arguments::of);
401
}
402
}
403
```
404
405
Custom sources provide unlimited flexibility for test data generation, enabling integration with external systems, dynamic data generation, and complex test scenarios that go beyond the built-in argument sources.