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-aggregation.mddocs/

0

# Argument Aggregation

1

2

System for combining multiple arguments into single parameter objects with type-safe access and custom aggregation logic.

3

4

## Capabilities

5

6

### @AggregateWith Annotation

7

8

Specifies ArgumentsAggregator for combining multiple arguments into a single parameter.

9

10

```java { .api }

11

/**

12

* Specifies an ArgumentsAggregator for combining multiple arguments

13

*/

14

@Target(ElementType.PARAMETER)

15

@Retention(RetentionPolicy.RUNTIME)

16

@Documented

17

@API(status = STABLE, since = "5.0")

18

@interface AggregateWith {

19

/**

20

* ArgumentsAggregator implementation class

21

*/

22

Class<? extends ArgumentsAggregator> value();

23

}

24

```

25

26

### ArgumentsAccessor Interface

27

28

Type-safe access to argument arrays with automatic conversion support.

29

30

```java { .api }

31

/**

32

* Provides type-safe access to arguments with automatic conversion

33

*/

34

@API(status = STABLE, since = "5.0")

35

interface ArgumentsAccessor {

36

37

/**

38

* Gets argument at index as Object

39

*/

40

Object get(int index);

41

42

/**

43

* Gets argument at index with type conversion

44

*/

45

<T> T get(int index, Class<T> requiredType);

46

47

/**

48

* Gets argument as Character

49

*/

50

Character getCharacter(int index);

51

52

/**

53

* Gets argument as Boolean

54

*/

55

Boolean getBoolean(int index);

56

57

/**

58

* Gets argument as Byte

59

*/

60

Byte getByte(int index);

61

62

/**

63

* Gets argument as Short

64

*/

65

Short getShort(int index);

66

67

/**

68

* Gets argument as Integer

69

*/

70

Integer getInteger(int index);

71

72

/**

73

* Gets argument as Long

74

*/

75

Long getLong(int index);

76

77

/**

78

* Gets argument as Float

79

*/

80

Float getFloat(int index);

81

82

/**

83

* Gets argument as Double

84

*/

85

Double getDouble(int index);

86

87

/**

88

* Gets argument as String

89

*/

90

String getString(int index);

91

92

/**

93

* Returns number of arguments

94

*/

95

int size();

96

97

/**

98

* Converts all arguments to Object array

99

*/

100

Object[] toArray();

101

102

/**

103

* Converts all arguments to List

104

*/

105

List<Object> toList();

106

107

/**

108

* Gets current invocation index (0-based)

109

*/

110

int getInvocationIndex();

111

}

112

```

113

114

### ArgumentsAggregator Interface

115

116

Contract for aggregating multiple arguments into a single object.

117

118

```java { .api }

119

/**

120

* Contract for aggregating multiple arguments into a single object

121

* Implementations must be thread-safe and have a no-args constructor

122

* or a single constructor whose parameters can be resolved by JUnit

123

*/

124

@API(status = STABLE, since = "5.0")

125

@FunctionalInterface

126

interface ArgumentsAggregator {

127

128

/**

129

* Aggregates multiple arguments into single object

130

*

131

* @param accessor type-safe access to arguments

132

* @param context parameter context for target type information

133

* @return aggregated object, may be null

134

* @throws ArgumentsAggregationException if aggregation fails

135

*/

136

Object aggregateArguments(ArgumentsAccessor accessor, ParameterContext context)

137

throws ArgumentsAggregationException;

138

}

139

```

140

141

**Usage Examples:**

142

143

```java

144

import org.junit.jupiter.params.ParameterizedTest;

145

import org.junit.jupiter.params.aggregator.AggregateWith;

146

import org.junit.jupiter.params.aggregator.ArgumentsAccessor;

147

import org.junit.jupiter.params.aggregator.ArgumentsAggregator;

148

import org.junit.jupiter.params.provider.CsvSource;

149

import org.junit.jupiter.api.extension.ParameterContext;

150

151

// Person class for aggregation examples

152

class Person {

153

private final String name;

154

private final int age;

155

private final boolean active;

156

157

Person(String name, int age, boolean active) {

158

this.name = name;

159

this.age = age;

160

this.active = active;

161

}

162

163

// Getters

164

public String getName() { return name; }

165

public int getAge() { return age; }

166

public boolean isActive() { return active; }

167

}

168

169

// Custom aggregator implementation

170

class PersonAggregator implements ArgumentsAggregator {

171

172

@Override

173

public Person aggregateArguments(ArgumentsAccessor accessor, ParameterContext context) {

174

return new Person(

175

accessor.getString(0),

176

accessor.getInteger(1),

177

accessor.getBoolean(2)

178

);

179

}

180

}

181

182

class ArgumentAggregationExamples {

183

184

// Direct ArgumentsAccessor usage

185

@ParameterizedTest

186

@CsvSource({

187

"Alice, 25, true",

188

"Bob, 30, false",

189

"Charlie, 35, true"

190

})

191

void testWithArgumentsAccessor(ArgumentsAccessor arguments) {

192

String name = arguments.getString(0);

193

int age = arguments.getInteger(1);

194

boolean active = arguments.getBoolean(2);

195

196

assertNotNull(name);

197

assertTrue(age > 0);

198

199

// Can also access by type conversion

200

String ageString = arguments.get(1, String.class);

201

assertEquals(String.valueOf(age), ageString);

202

}

203

204

// Custom aggregator usage

205

@ParameterizedTest

206

@CsvSource({

207

"Alice, 25, true",

208

"Bob, 30, false",

209

"Charlie, 35, true"

210

})

211

void testWithCustomAggregator(@AggregateWith(PersonAggregator.class) Person person) {

212

assertNotNull(person);

213

assertNotNull(person.getName());

214

assertTrue(person.getAge() > 0);

215

}

216

217

// Mixed parameters with aggregation

218

@ParameterizedTest

219

@CsvSource({

220

"1, Alice, 25, true",

221

"2, Bob, 30, false"

222

})

223

void testMixedParameters(int id, @AggregateWith(PersonAggregator.class) Person person) {

224

assertTrue(id > 0);

225

assertNotNull(person);

226

assertNotNull(person.getName());

227

}

228

}

229

```

230

231

### Advanced Aggregation Patterns

232

233

**Generic aggregator for any class:**

234

235

```java

236

import java.lang.reflect.Constructor;

237

238

class ReflectiveAggregator implements ArgumentsAggregator {

239

240

@Override

241

public Object aggregateArguments(ArgumentsAccessor accessor, ParameterContext context)

242

throws ArgumentsAggregationException {

243

try {

244

Class<?> targetType = context.getParameter().getType();

245

Constructor<?> constructor = findMatchingConstructor(targetType, accessor.size());

246

247

Object[] args = new Object[accessor.size()];

248

Class<?>[] paramTypes = constructor.getParameterTypes();

249

250

for (int i = 0; i < accessor.size(); i++) {

251

args[i] = accessor.get(i, paramTypes[i]);

252

}

253

254

return constructor.newInstance(args);

255

} catch (Exception e) {

256

throw new ArgumentsAggregationException("Aggregation failed", e);

257

}

258

}

259

260

private Constructor<?> findMatchingConstructor(Class<?> clazz, int argCount) {

261

for (Constructor<?> constructor : clazz.getConstructors()) {

262

if (constructor.getParameterCount() == argCount) {

263

return constructor;

264

}

265

}

266

throw new RuntimeException("No matching constructor found");

267

}

268

}

269

270

class Product {

271

private final String name;

272

private final double price;

273

private final boolean available;

274

275

public Product(String name, double price, boolean available) {

276

this.name = name;

277

this.price = price;

278

this.available = available;

279

}

280

281

// Getters

282

public String getName() { return name; }

283

public double getPrice() { return price; }

284

public boolean isAvailable() { return available; }

285

}

286

287

class ReflectiveAggregationExample {

288

289

@ParameterizedTest

290

@CsvSource({

291

"Apple, 1.50, true",

292

"Banana, 0.75, false"

293

})

294

void testReflectiveAggregation(@AggregateWith(ReflectiveAggregator.class) Product product) {

295

assertNotNull(product);

296

assertNotNull(product.getName());

297

assertTrue(product.getPrice() >= 0);

298

}

299

}

300

```

301

302

**Builder pattern aggregator:**

303

304

```java

305

class UserBuilder {

306

private String name;

307

private int age;

308

private String email;

309

private boolean active = true;

310

311

public UserBuilder name(String name) {

312

this.name = name;

313

return this;

314

}

315

316

public UserBuilder age(int age) {

317

this.age = age;

318

return this;

319

}

320

321

public UserBuilder email(String email) {

322

this.email = email;

323

return this;

324

}

325

326

public UserBuilder active(boolean active) {

327

this.active = active;

328

return this;

329

}

330

331

public User build() {

332

return new User(name, age, email, active);

333

}

334

}

335

336

class User {

337

private final String name, email;

338

private final int age;

339

private final boolean active;

340

341

User(String name, int age, String email, boolean active) {

342

this.name = name;

343

this.age = age;

344

this.email = email;

345

this.active = active;

346

}

347

348

// Getters

349

public String getName() { return name; }

350

public String getEmail() { return email; }

351

public int getAge() { return age; }

352

public boolean isActive() { return active; }

353

}

354

355

class UserBuilderAggregator implements ArgumentsAggregator {

356

357

@Override

358

public User aggregateArguments(ArgumentsAccessor accessor, ParameterContext context) {

359

return new UserBuilder()

360

.name(accessor.getString(0))

361

.age(accessor.getInteger(1))

362

.email(accessor.getString(2))

363

.active(accessor.getBoolean(3))

364

.build();

365

}

366

}

367

368

class BuilderAggregationExample {

369

370

@ParameterizedTest

371

@CsvSource({

372

"Alice, 25, alice@example.com, true",

373

"Bob, 30, bob@example.com, false"

374

})

375

void testBuilderAggregation(@AggregateWith(UserBuilderAggregator.class) User user) {

376

assertNotNull(user);

377

assertNotNull(user.getName());

378

assertTrue(user.getEmail().contains("@"));

379

assertTrue(user.getAge() > 0);

380

}

381

}

382

```

383

384

### Exception Types

385

386

Exceptions thrown during argument aggregation operations.

387

388

```java { .api }

389

/**

390

* Exception thrown when argument access fails

391

*/

392

@API(status = STABLE, since = "5.0")

393

class ArgumentAccessException extends JUnitException {

394

395

ArgumentAccessException(String message) {

396

super(message);

397

}

398

399

ArgumentAccessException(String message, Throwable cause) {

400

super(message, cause);

401

}

402

}

403

404

/**

405

* Exception thrown when argument aggregation fails

406

*/

407

@API(status = STABLE, since = "5.0")

408

class ArgumentsAggregationException extends JUnitException {

409

410

ArgumentsAggregationException(String message) {

411

super(message);

412

}

413

414

ArgumentsAggregationException(String message, Throwable cause) {

415

super(message, cause);

416

}

417

}

418

```

419

420

### DefaultArgumentsAccessor

421

422

Default implementation of ArgumentsAccessor interface.

423

424

```java { .api }

425

/**

426

* Default implementation of ArgumentsAccessor

427

*/

428

@API(status = STABLE, since = "5.0")

429

class DefaultArgumentsAccessor implements ArgumentsAccessor {

430

431

private final Object[] arguments;

432

private final int invocationIndex;

433

434

/**

435

* Creates accessor for given arguments and invocation index

436

*/

437

DefaultArgumentsAccessor(Object[] arguments, int invocationIndex) {

438

this.arguments = arguments;

439

this.invocationIndex = invocationIndex;

440

}

441

442

// Implementation of all ArgumentsAccessor methods...

443

}

444

```

445

446

### Complex Aggregation Examples

447

448

**Map-based aggregation:**

449

450

```java

451

class MapAggregator implements ArgumentsAggregator {

452

453

@Override

454

public Map<String, Object> aggregateArguments(ArgumentsAccessor accessor,

455

ParameterContext context) {

456

Map<String, Object> result = new HashMap<>();

457

458

// Assume even indices are keys, odd indices are values

459

for (int i = 0; i < accessor.size() - 1; i += 2) {

460

String key = accessor.getString(i);

461

Object value = accessor.get(i + 1);

462

result.put(key, value);

463

}

464

465

return result;

466

}

467

}

468

469

class MapAggregationExample {

470

471

@ParameterizedTest

472

@CsvSource({

473

"name, Alice, age, 25, active, true",

474

"name, Bob, age, 30, active, false"

475

})

476

void testMapAggregation(@AggregateWith(MapAggregator.class) Map<String, Object> data) {

477

assertNotNull(data);

478

assertTrue(data.containsKey("name"));

479

assertTrue(data.containsKey("age"));

480

assertTrue(data.containsKey("active"));

481

482

String name = (String) data.get("name");

483

assertNotNull(name);

484

}

485

}

486

```

487

488

**Validation during aggregation:**

489

490

```java

491

class ValidatingPersonAggregator implements ArgumentsAggregator {

492

493

@Override

494

public Person aggregateArguments(ArgumentsAccessor accessor, ParameterContext context)

495

throws ArgumentsAggregationException {

496

String name = accessor.getString(0);

497

int age = accessor.getInteger(1);

498

boolean active = accessor.getBoolean(2);

499

500

// Validation logic

501

if (name == null || name.trim().isEmpty()) {

502

throw new ArgumentsAggregationException("Name cannot be empty");

503

}

504

505

if (age < 0 || age > 150) {

506

throw new ArgumentsAggregationException("Invalid age: " + age);

507

}

508

509

return new Person(name, age, active);

510

}

511

}

512

513

class ValidatingAggregationExample {

514

515

@ParameterizedTest

516

@CsvSource({

517

"Alice, 25, true",

518

"Bob, 30, false"

519

})

520

void testValidatingAggregation(@AggregateWith(ValidatingPersonAggregator.class) Person person) {

521

assertNotNull(person);

522

assertFalse(person.getName().trim().isEmpty());

523

assertTrue(person.getAge() >= 0 && person.getAge() <= 150);

524

}

525

}

526

```

527

528

The argument aggregation system provides powerful capabilities for combining multiple test arguments into cohesive objects, enabling clean test method signatures and complex parameter validation while maintaining type safety and clear error reporting.