0
# Argument Conversion
1
2
Type conversion system with built-in converters and extension points for custom conversions between argument types.
3
4
## Capabilities
5
6
### @ConvertWith Annotation
7
8
Specifies explicit ArgumentConverter for parameter conversion.
9
10
```java { .api }
11
/**
12
* Specifies an explicit ArgumentConverter for parameter conversion
13
*/
14
@Target(ElementType.PARAMETER)
15
@Retention(RetentionPolicy.RUNTIME)
16
@Documented
17
@API(status = STABLE, since = "5.0")
18
@interface ConvertWith {
19
/**
20
* ArgumentConverter implementation class
21
*/
22
Class<? extends ArgumentConverter> value();
23
}
24
```
25
26
### ArgumentConverter Interface
27
28
Core contract for converting arguments to target parameter types.
29
30
```java { .api }
31
/**
32
* Contract for converting arguments to target types
33
* Implementations must be thread-safe and have a no-args constructor
34
* or a single constructor whose parameters can be resolved by JUnit
35
*/
36
@API(status = STABLE, since = "5.0")
37
@FunctionalInterface
38
interface ArgumentConverter {
39
/**
40
* Converts the supplied source object to target type
41
*
42
* @param source the source object to convert
43
* @param context parameter context providing target type information
44
* @return converted object, may be null
45
* @throws ArgumentConversionException if conversion fails
46
*/
47
Object convert(Object source, ParameterContext context)
48
throws ArgumentConversionException;
49
}
50
```
51
52
### SimpleArgumentConverter
53
54
Abstract base class for converters that only need the target type.
55
56
```java { .api }
57
/**
58
* Abstract base class for converters that only need target type information
59
*/
60
@API(status = STABLE, since = "5.0")
61
abstract class SimpleArgumentConverter implements ArgumentConverter {
62
63
@Override
64
public final Object convert(Object source, ParameterContext context)
65
throws ArgumentConversionException {
66
return convert(source, context.getParameter().getType());
67
}
68
69
/**
70
* Converts source to target type
71
*
72
* @param source source object to convert, may be null
73
* @param targetType target type for conversion
74
* @return converted object, may be null
75
* @throws ArgumentConversionException if conversion fails
76
*/
77
protected abstract Object convert(Object source, Class<?> targetType)
78
throws ArgumentConversionException;
79
}
80
```
81
82
### TypedArgumentConverter
83
84
Abstract base class for type-safe source-to-target conversion.
85
86
```java { .api }
87
/**
88
* Abstract base class for type-safe source-to-target conversion
89
*/
90
@API(status = STABLE, since = "5.0")
91
abstract class TypedArgumentConverter<S, T> implements ArgumentConverter {
92
93
private final Class<S> sourceType;
94
private final Class<T> targetType;
95
96
/**
97
* Creates converter for specific source and target types
98
*/
99
protected TypedArgumentConverter(Class<S> sourceType, Class<T> targetType) {
100
this.sourceType = sourceType;
101
this.targetType = targetType;
102
}
103
104
@Override
105
public final Object convert(Object source, ParameterContext context)
106
throws ArgumentConversionException {
107
if (source == null) {
108
return null;
109
}
110
111
if (!sourceType.isInstance(source)) {
112
throw new ArgumentConversionException(
113
"Cannot convert " + source.getClass() + " to " + targetType);
114
}
115
116
return convert(sourceType.cast(source));
117
}
118
119
/**
120
* Converts source to target type with type safety
121
*
122
* @param source source object of type S, never null
123
* @return converted object of type T, may be null
124
* @throws ArgumentConversionException if conversion fails
125
*/
126
protected abstract T convert(S source) throws ArgumentConversionException;
127
}
128
```
129
130
**Usage Examples:**
131
132
```java
133
import org.junit.jupiter.params.ParameterizedTest;
134
import org.junit.jupiter.params.converter.ConvertWith;
135
import org.junit.jupiter.params.converter.SimpleArgumentConverter;
136
import org.junit.jupiter.params.converter.TypedArgumentConverter;
137
import org.junit.jupiter.params.provider.ValueSource;
138
import java.time.LocalDate;
139
import java.util.Locale;
140
141
// Simple converter example
142
class StringToUpperCaseConverter extends SimpleArgumentConverter {
143
144
@Override
145
protected Object convert(Object source, Class<?> targetType) {
146
if (source instanceof String && targetType == String.class) {
147
return ((String) source).toUpperCase();
148
}
149
throw new ArgumentConversionException("Cannot convert to " + targetType);
150
}
151
}
152
153
// Typed converter example
154
class StringToLocalDateConverter extends TypedArgumentConverter<String, LocalDate> {
155
156
StringToLocalDateConverter() {
157
super(String.class, LocalDate.class);
158
}
159
160
@Override
161
protected LocalDate convert(String source) {
162
return LocalDate.parse(source);
163
}
164
}
165
166
// Advanced converter with configuration
167
class StringToLocaleConverter implements ArgumentConverter {
168
169
@Override
170
public Object convert(Object source, ParameterContext context) {
171
if (source instanceof String) {
172
String[] parts = ((String) source).split("_");
173
if (parts.length == 1) {
174
return new Locale(parts[0]);
175
} else if (parts.length == 2) {
176
return new Locale(parts[0], parts[1]);
177
}
178
}
179
throw new ArgumentConversionException("Cannot convert to Locale: " + source);
180
}
181
}
182
183
class ArgumentConverterExamples {
184
185
@ParameterizedTest
186
@ValueSource(strings = {"hello", "world", "junit"})
187
void testWithUpperCase(@ConvertWith(StringToUpperCaseConverter.class) String value) {
188
assertEquals(value, value.toUpperCase());
189
// Tests: "HELLO", "WORLD", "JUNIT"
190
}
191
192
@ParameterizedTest
193
@ValueSource(strings = {"2023-01-01", "2023-12-31", "2024-02-29"})
194
void testWithDates(@ConvertWith(StringToLocalDateConverter.class) LocalDate date) {
195
assertNotNull(date);
196
assertTrue(date.getYear() >= 2023);
197
}
198
199
@ParameterizedTest
200
@ValueSource(strings = {"en", "en_US", "fr_FR"})
201
void testWithLocales(@ConvertWith(StringToLocaleConverter.class) Locale locale) {
202
assertNotNull(locale);
203
assertNotNull(locale.getLanguage());
204
}
205
}
206
```
207
208
### Java Time Conversion
209
210
Built-in support for Java Time types with pattern-based conversion.
211
212
```java { .api }
213
/**
214
* Converts strings to Java Time types using specified pattern
215
*/
216
@Target(ElementType.PARAMETER)
217
@Retention(RetentionPolicy.RUNTIME)
218
@Documented
219
@API(status = STABLE, since = "5.0")
220
@ConvertWith(JavaTimeArgumentConverter.class)
221
@interface JavaTimeConversionPattern {
222
/**
223
* Date/time pattern string
224
*/
225
String value();
226
227
/**
228
* Whether to allow null values (experimental)
229
*/
230
@API(status = EXPERIMENTAL, since = "5.12")
231
boolean nullable() default false;
232
}
233
```
234
235
**Java Time Conversion Examples:**
236
237
```java
238
import org.junit.jupiter.params.ParameterizedTest;
239
import org.junit.jupiter.params.converter.JavaTimeConversionPattern;
240
import org.junit.jupiter.params.provider.ValueSource;
241
import java.time.*;
242
243
class JavaTimeConversionExamples {
244
245
@ParameterizedTest
246
@ValueSource(strings = {"2023-01-01", "2023-12-31"})
247
void testWithLocalDate(LocalDate date) {
248
// Automatic conversion from ISO date strings
249
assertNotNull(date);
250
assertEquals(2023, date.getYear());
251
}
252
253
@ParameterizedTest
254
@ValueSource(strings = {"01/15/2023", "12/31/2023"})
255
void testWithCustomDatePattern(
256
@JavaTimeConversionPattern("MM/dd/yyyy") LocalDate date) {
257
assertNotNull(date);
258
assertEquals(2023, date.getYear());
259
}
260
261
@ParameterizedTest
262
@ValueSource(strings = {"14:30:00", "09:15:30"})
263
void testWithLocalTime(LocalTime time) {
264
// Automatic conversion from ISO time strings
265
assertNotNull(time);
266
}
267
268
@ParameterizedTest
269
@ValueSource(strings = {"2:30 PM", "9:15 AM"})
270
void testWithCustomTimePattern(
271
@JavaTimeConversionPattern("h:mm a") LocalTime time) {
272
assertNotNull(time);
273
}
274
275
@ParameterizedTest
276
@ValueSource(strings = {"2023-01-01T14:30:00", "2023-12-31T23:59:59"})
277
void testWithLocalDateTime(LocalDateTime dateTime) {
278
// Automatic conversion from ISO datetime strings
279
assertNotNull(dateTime);
280
assertEquals(2023, dateTime.getYear());
281
}
282
283
@ParameterizedTest
284
@ValueSource(strings = {"Jan 15, 2023 2:30:00 PM", "Dec 31, 2023 11:59:59 PM"})
285
void testWithCustomDateTimePattern(
286
@JavaTimeConversionPattern("MMM dd, yyyy h:mm:ss a") LocalDateTime dateTime) {
287
assertNotNull(dateTime);
288
assertEquals(2023, dateTime.getYear());
289
}
290
}
291
```
292
293
### Default Argument Converter
294
295
Built-in converter that handles common implicit conversions.
296
297
```java { .api }
298
/**
299
* Default converter providing implicit conversions for common types
300
*/
301
@API(status = STABLE, since = "5.0")
302
class DefaultArgumentConverter {
303
304
/**
305
* Converts source to target type using implicit conversion rules
306
*/
307
public static Object convert(Object source, Class<?> targetType)
308
throws ArgumentConversionException {
309
// Implementation handles:
310
// - String to primitive types and wrappers
311
// - String to enums
312
// - String to java.time types
313
// - String to java.io.File, java.nio.file.Path
314
// - String to java.net.URI, java.net.URL
315
// - String to Currency, Locale, UUID
316
// - Number conversions
317
// - And more...
318
}
319
}
320
```
321
322
### Annotation-Based Converters
323
324
Interface for converters that consume configuration annotations.
325
326
```java { .api }
327
/**
328
* Base interface for annotation-driven argument converters
329
*/
330
@API(status = STABLE, since = "5.0")
331
interface AnnotationBasedArgumentConverter<A extends Annotation, S, T>
332
extends ArgumentConverter, AnnotationConsumer<A> {
333
334
/**
335
* Converts source to target type using annotation configuration
336
*/
337
T convert(S source, Class<T> targetType) throws ArgumentConversionException;
338
}
339
```
340
341
### Argument Conversion Exception
342
343
Exception thrown when argument conversion fails.
344
345
```java { .api }
346
/**
347
* Exception thrown when argument conversion fails
348
*/
349
@API(status = STABLE, since = "5.0")
350
class ArgumentConversionException extends JUnitException {
351
352
/**
353
* Constructs exception with message
354
*/
355
ArgumentConversionException(String message) {
356
super(message);
357
}
358
359
/**
360
* Constructs exception with message and cause
361
*/
362
ArgumentConversionException(String message, Throwable cause) {
363
super(message, cause);
364
}
365
}
366
```
367
368
### Advanced Conversion Patterns
369
370
**Custom conversion with validation:**
371
372
```java
373
class ValidatedEmailConverter extends TypedArgumentConverter<String, Email> {
374
375
ValidatedEmailConverter() {
376
super(String.class, Email.class);
377
}
378
379
@Override
380
protected Email convert(String source) throws ArgumentConversionException {
381
if (!source.contains("@")) {
382
throw new ArgumentConversionException("Invalid email format: " + source);
383
}
384
return new Email(source);
385
}
386
}
387
388
class Email {
389
private final String address;
390
391
Email(String address) {
392
this.address = address;
393
}
394
395
public String getAddress() {
396
return address;
397
}
398
}
399
400
class ValidationExample {
401
402
@ParameterizedTest
403
@ValueSource(strings = {"user@example.com", "test@domain.org"})
404
void testValidEmails(@ConvertWith(ValidatedEmailConverter.class) Email email) {
405
assertNotNull(email);
406
assertTrue(email.getAddress().contains("@"));
407
}
408
}
409
```
410
411
**Complex object conversion:**
412
413
```java
414
class JsonToObjectConverter implements ArgumentConverter {
415
416
@Override
417
public Object convert(Object source, ParameterContext context)
418
throws ArgumentConversionException {
419
if (source instanceof String) {
420
Class<?> targetType = context.getParameter().getType();
421
return parseJson((String) source, targetType);
422
}
423
throw new ArgumentConversionException("Cannot convert " + source.getClass());
424
}
425
426
private Object parseJson(String json, Class<?> targetType) {
427
// JSON parsing implementation
428
// This is a simplified example
429
return null;
430
}
431
}
432
433
class JsonConversionExample {
434
435
@ParameterizedTest
436
@ValueSource(strings = {
437
"{\"name\":\"Alice\",\"age\":25}",
438
"{\"name\":\"Bob\",\"age\":30}"
439
})
440
void testJsonConversion(@ConvertWith(JsonToObjectConverter.class) Person person) {
441
assertNotNull(person);
442
assertNotNull(person.getName());
443
assertTrue(person.getAge() > 0);
444
}
445
}
446
```
447
448
The argument conversion system provides flexible type transformation capabilities, enabling seamless integration between various data sources and test parameter types while maintaining type safety and clear error reporting.