or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

argument-aggregation.mdargument-conversion.mdcore-testing.mdcsv-sources.mdcustom-sources.mdenum-method-sources.mdindex.mdvalue-sources.md

argument-conversion.mddocs/

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.