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

custom-sources.mddocs/

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.