0
# Custom Resolution and Configuration
1
2
This document covers advanced features including custom data provider method resolvers, test name formatting with placeholders, and filtering capabilities.
3
4
## Required Imports
5
6
```java
7
import com.tngtech.java.junit.dataprovider.DataProviderMethodResolver;
8
import com.tngtech.java.junit.dataprovider.internal.DefaultDataProviderMethodResolver;
9
import com.tngtech.java.junit.dataprovider.Placeholders;
10
import com.tngtech.java.junit.dataprovider.internal.placeholder.BasePlaceholder;
11
import org.junit.runners.model.FrameworkMethod;
12
import org.junit.runners.model.TestClass;
13
import java.lang.reflect.Method;
14
import java.util.List;
15
```
16
17
## Custom Data Provider Method Resolvers
18
19
The DataProviderMethodResolver interface allows you to implement custom logic for finding data provider methods.
20
21
```java { .api }
22
public interface DataProviderMethodResolver {
23
24
/**
25
* Returns dataprovider methods for the given test method.
26
* @param testMethod test method that uses a dataprovider
27
* @param useDataProvider UseDataProvider annotation on the test method
28
* @return resolved dataprovider methods or empty list if none found
29
* @throws IllegalArgumentException if testMethod is null
30
*/
31
List<FrameworkMethod> resolve(FrameworkMethod testMethod, UseDataProvider useDataProvider);
32
}
33
```
34
35
### DefaultDataProviderMethodResolver
36
37
The built-in resolver that implements convention-based method resolution:
38
39
```java { .api }
40
public class DefaultDataProviderMethodResolver implements DataProviderMethodResolver {
41
42
/**
43
* Resolve using convention-based strategies in this order:
44
* 1. Explicit name via UseDataProvider.value()
45
* 2. Method name equals test method name
46
* 3. Replace "test" prefix with "dataProvider" or "data"
47
* 4. Add "dataProvider" or "data" prefix to capitalized method name
48
* @param testMethod test method that uses a dataprovider
49
* @param useDataProvider UseDataProvider annotation on the test method
50
* @return resolved dataprovider methods or empty list if none found
51
*/
52
public List<FrameworkMethod> resolve(FrameworkMethod testMethod, UseDataProvider useDataProvider)
53
54
/**
55
* Find classes where data provider methods should be searched.
56
* @param testMethod test method context
57
* @param locations specified location classes, or test class if empty
58
* @return list of TestClass objects to search in
59
*/
60
protected List<TestClass> findDataProviderLocations(FrameworkMethod testMethod, Class<?>[] locations)
61
62
/**
63
* Search for data provider methods across multiple locations.
64
* @param locations TestClass objects to search in
65
* @param testMethodName name of the test method
66
* @param value explicit data provider name or DEFAULT_VALUE for convention
67
* @return list of matching data provider methods
68
*/
69
protected List<FrameworkMethod> findDataProviderMethods(List<TestClass> locations, String testMethodName, String value)
70
71
/**
72
* Find data provider method in a specific location using naming conventions.
73
* @param location TestClass to search in
74
* @param testMethodName name of the test method
75
* @param value explicit data provider name or DEFAULT_VALUE for convention
76
* @return matching data provider method or null if not found
77
*/
78
protected FrameworkMethod findDataProviderMethod(TestClass location, String testMethodName, String value)
79
}
80
```
81
82
### Custom Resolver Implementation
83
84
Create a custom resolver for specialized naming or lookup strategies:
85
86
```java
87
public class PrefixBasedResolver implements DataProviderMethodResolver {
88
89
@Override
90
public List<FrameworkMethod> resolve(FrameworkMethod testMethod, UseDataProvider useDataProvider) {
91
String testMethodName = testMethod.getName();
92
String dataProviderName;
93
94
if (!UseDataProvider.DEFAULT_VALUE.equals(useDataProvider.value())) {
95
dataProviderName = useDataProvider.value();
96
} else {
97
// Custom logic: look for methods with "dp_" prefix
98
dataProviderName = "dp_" + testMethodName;
99
}
100
101
TestClass testClass = new TestClass(testMethod.getMethod().getDeclaringClass());
102
List<FrameworkMethod> dataProviderMethods = testClass.getAnnotatedMethods(DataProvider.class);
103
104
for (FrameworkMethod method : dataProviderMethods) {
105
if (method.getName().equals(dataProviderName)) {
106
return Arrays.asList(method);
107
}
108
}
109
110
return Collections.emptyList();
111
}
112
}
113
114
// Usage
115
@Test
116
@UseDataProvider(resolver = PrefixBasedResolver.class)
117
public void testStringLength(String input, int expected) {
118
assertEquals(expected, input.length());
119
}
120
121
@DataProvider
122
public static Object[][] dp_testStringLength() { // Custom prefix
123
return new Object[][] { {"hello", 5}, {"world", 5} };
124
}
125
```
126
127
### Multiple Location Resolution
128
129
Use data providers from multiple classes:
130
131
```java
132
public class CommonDataProviders {
133
@DataProvider
134
public static Object[][] commonStringData() {
135
return new Object[][] { {"test", 4}, {"hello", 5} };
136
}
137
}
138
139
public class SpecialDataProviders {
140
@DataProvider
141
public static Object[][] specialStringData() {
142
return new Object[][] { {"special", 7}, {"unique", 6} };
143
}
144
}
145
146
@Test
147
@UseDataProvider(
148
value = "commonStringData",
149
location = { CommonDataProviders.class, SpecialDataProviders.class }
150
)
151
public void testWithMultipleLocations(String input, int length) {
152
assertEquals(length, input.length());
153
}
154
```
155
156
### Resolver Strategies
157
158
Control how multiple resolvers are used:
159
160
```java
161
@Test
162
@UseDataProvider(
163
resolver = { CustomResolver1.class, CustomResolver2.class },
164
resolveStrategy = ResolveStrategy.UNTIL_FIRST_MATCH // Default
165
)
166
public void testFirstMatch(Object data) {
167
// Uses data from first resolver that finds a match
168
}
169
170
@Test
171
@UseDataProvider(
172
resolver = { CustomResolver1.class, CustomResolver2.class },
173
resolveStrategy = ResolveStrategy.AGGREGATE_ALL_MATCHES
174
)
175
public void testAggregated(Object data) {
176
// Combines data from all resolvers that find matches
177
}
178
```
179
180
## Test Method Name Formatting
181
182
Customize how parameterized test names are displayed using format patterns and placeholders.
183
184
### Format Patterns
185
186
The @DataProvider annotation supports format patterns to control test method naming:
187
188
```java { .api }
189
@DataProvider(format = "%m[%i: %p[0..-1]]") // Default format
190
@DataProvider(format = "%m[%i]") // Method name with index only
191
@DataProvider(format = "%m: %p[0]") // Method name with first parameter
192
@DataProvider(format = "%c.%m(%p[0..1])") // Class.method(first two params)
193
```
194
195
### Available Placeholders
196
197
```java { .api }
198
// Class placeholders
199
%c // Simple class name (e.g., "DataProviderTest")
200
%cc // Canonical class name (e.g., "com.example.DataProviderTest")
201
202
// Method placeholders
203
%m // Simple method name (e.g., "testStringLength")
204
%cm // Complete method signature (e.g., "com.example.Test.testStringLength(java.lang.String)")
205
206
// Index placeholder
207
%i // Data provider index (0, 1, 2, ...)
208
209
// Parameter placeholders
210
%p[0] // First parameter
211
%p[-1] // Last parameter
212
%p[1..3] // Parameters 1 through 3
213
%p[0..-2] // All parameters except last
214
%p[0..-1] // All parameters
215
```
216
217
### Formatting Examples
218
219
```java
220
@DataProvider(format = "%m[%i: %p[0]]")
221
public static Object[][] stringLengthData() {
222
return new Object[][] {
223
{ "hello", 5 },
224
{ "world", 5 },
225
{ "", 0 }
226
};
227
}
228
// Test names: testStringLength[0: hello], testStringLength[1: world], testStringLength[2: ]
229
230
@DataProvider(format = "%c.%m: %p[0] -> %p[1]")
231
public static Object[][] conversionData() {
232
return new Object[][] {
233
{ "123", 123 },
234
{ "true", true }
235
};
236
}
237
// Test names: MyTest.testConversion: 123 -> 123, MyTest.testConversion: true -> true
238
239
@DataProvider(format = "%m(%p[0..1]) #%i")
240
public static Object[][] rangeData() {
241
return new Object[][] {
242
{ 1, 10, "range1" },
243
{ 20, 30, "range2" }
244
};
245
}
246
// Test names: testRange(1, 10) #0, testRange(20, 30) #1
247
```
248
249
## Custom Placeholders
250
251
Extend the placeholder system with custom formatting logic.
252
253
### Placeholders Utility Class
254
255
The Placeholders class manages the collection of placeholder implementations used for test method name formatting.
256
257
```java { .api }
258
public class Placeholders {
259
260
/**
261
* Get all registered placeholders. Returns modifiable list.
262
* @return all BasePlaceholder instances for format processing
263
*/
264
public static List<BasePlaceholder> all()
265
266
/**
267
* Reset to default placeholders. Clears all custom placeholders and
268
* restores the default set: %c, %cc, %m, %cm, %i, %p[x]
269
*/
270
public static void reset()
271
}
272
```
273
274
The default placeholders included are:
275
- CanonicalClassNamePlaceholder (%cc) - Full canonical class name
276
- CompleteMethodSignaturePlaceholder (%cm) - Complete method signature
277
- IndexPlaceholder (%i) - Data provider row index
278
- ParameterPlaceholder (%p[x]) - Parameter access with indexing
279
- SimpleClassNamePlaceholder (%c) - Simple class name
280
- SimpleMethodNamePlaceholder (%m) - Method name
281
282
### Creating Custom Placeholders
283
284
Implement BasePlaceholder for custom formatting:
285
286
```java { .api }
287
public abstract class BasePlaceholder {
288
289
/**
290
* Constructor requires placeholder regex pattern.
291
* @param placeholderRegex regular expression to match placeholder
292
*/
293
public BasePlaceholder(String placeholderRegex)
294
295
/**
296
* Set context for placeholder processing.
297
* @param method test method being processed
298
* @param idx data provider row index
299
* @param parameters test method parameters
300
*/
301
public void setContext(Method method, int idx, Object[] parameters)
302
303
/**
304
* Process format string, replacing placeholder occurrences.
305
* @param formatPattern input format pattern
306
* @return processed format string with replacements
307
*/
308
public String process(String formatPattern)
309
310
/**
311
* Generate replacement for found placeholder. Override this method.
312
* @param placeholder matched placeholder string
313
* @return replacement string for the placeholder
314
*/
315
protected abstract String getReplacementFor(String placeholder);
316
}
317
```
318
319
Example custom placeholder:
320
321
```java
322
public class TimestampPlaceholder extends BasePlaceholder {
323
private String timestamp;
324
325
public TimestampPlaceholder() {
326
super("%t"); // Regex pattern to match %t placeholder
327
}
328
329
@Override
330
public void setContext(Method method, int idx, Object[] parameters) {
331
this.timestamp = Instant.now().toString();
332
}
333
334
@Override
335
protected String getReplacementFor(String placeholder) {
336
return timestamp;
337
}
338
}
339
340
// Register custom placeholder
341
static {
342
Placeholders.all().add(0, new TimestampPlaceholder());
343
}
344
345
@DataProvider(format = "%m[%i] at %t")
346
public static Object[][] timestampedData() {
347
return new Object[][] { {"test1"}, {"test2"} };
348
}
349
// Test names: testMethod[0] at 2023-12-01T10:30:00Z, testMethod[1] at 2023-12-01T10:30:00Z
350
```
351
352
### Parameter Transformation Placeholder
353
354
```java
355
public class HashPlaceholder extends BasePlaceholder {
356
private Object[] parameters;
357
358
public HashPlaceholder() {
359
super("%hash"); // Regex pattern to match %hash placeholder
360
}
361
362
@Override
363
public void setContext(Method method, int idx, Object[] parameters) {
364
this.parameters = parameters;
365
}
366
367
@Override
368
protected String getReplacementFor(String placeholder) {
369
return String.valueOf(Arrays.hashCode(parameters));
370
}
371
}
372
373
@DataProvider(format = "%m[%hash]")
374
public static Object[][] hashedData() {
375
return new Object[][] {
376
{"param1", "param2"},
377
{"param3", "param4"}
378
};
379
}
380
// Test names: testMethod[12345], testMethod[67890] (hash values)
381
```
382
383
## Filtering and Test Selection
384
385
Control which tests run using JUnit filters enhanced for data provider support.
386
387
### DataProviderFilter
388
389
```java { .api }
390
public class DataProviderFilter extends Filter {
391
392
/**
393
* Create filter that supports data provider row filtering.
394
* @param filter original filter to wrap
395
*/
396
public DataProviderFilter(Filter filter)
397
398
/**
399
* Determine if test should run, supporting data provider syntax.
400
* @param description test description
401
* @return true if test should run
402
*/
403
public boolean shouldRun(Description description)
404
405
/**
406
* Get description of wrapped filter.
407
* @return filter description
408
*/
409
public String describe()
410
411
// Pattern constants for parsing test descriptions
412
static final Pattern DESCRIPTION_PATTERN;
413
static final Pattern GENEROUS_DESCRIPTION_PATTERN;
414
}
415
```
416
417
### Filter Usage
418
419
The DataProviderRunner automatically wraps filters to support data provider test selection:
420
421
```java
422
// These filtering patterns work with DataProviderFilter:
423
// "testMethod" - runs all data provider rows for testMethod
424
// "testMethod[0]" - runs only row 0 of testMethod data provider
425
// "testMethod[0: param1, param2]" - runs specific parameterized test
426
// "com.example.TestClass" - runs all tests in TestClass
427
```
428
429
### Programmatic Filtering
430
431
```java
432
@RunWith(DataProviderRunner.class)
433
public class FilteredTest {
434
435
@DataProvider
436
public static Object[][] allData() {
437
return new Object[][] {
438
{"test1", 1}, {"test2", 2}, {"test3", 3}
439
};
440
}
441
442
@Test
443
@UseDataProvider("allData")
444
public void testFiltered(String name, int value) {
445
// Only specific rows will run based on filter
446
}
447
}
448
449
// Run only second data row:
450
// mvn test -Dtest="FilteredTest#testFiltered[1]*"
451
```
452
453
### Custom Filter Integration
454
455
Integrate with custom JUnit filters:
456
457
```java
458
public class CustomTestFilter extends Filter {
459
@Override
460
public boolean shouldRun(Description description) {
461
// Custom filtering logic
462
return description.getDisplayName().contains("important");
463
}
464
465
@Override
466
public String describe() {
467
return "Custom filter for important tests";
468
}
469
}
470
471
// DataProviderRunner automatically wraps custom filters:
472
runner.filter(new CustomTestFilter());
473
// Becomes: runner.filter(new DataProviderFilter(new CustomTestFilter()));
474
```
475
476
## Integration with Test Frameworks
477
478
### JUnit Categories
479
480
Data provider tests work seamlessly with JUnit Categories:
481
482
```java
483
public interface SlowTests {}
484
public interface FastTests {}
485
486
@RunWith(DataProviderRunner.class)
487
@Category({FastTests.class})
488
public class CategorizedTest {
489
490
@DataProvider
491
public static Object[][] quickData() {
492
return new Object[][] { {"quick1"}, {"quick2"} };
493
}
494
495
@Test
496
@UseDataProvider("quickData")
497
@Category(SlowTests.class) // Override class-level category
498
public void testCategorized(String input) {
499
// Test logic
500
}
501
}
502
```
503
504
### Custom Runners and Extensions
505
506
Extend DataProviderRunner for specialized behavior:
507
508
```java
509
public class CustomDataProviderRunner extends DataProviderRunner {
510
511
public CustomDataProviderRunner(Class<?> clazz) throws InitializationError {
512
super(clazz);
513
}
514
515
@Override
516
protected void initializeHelpers() {
517
super.initializeHelpers();
518
// Customize data converter, test generator, or validator
519
this.dataConverter = new CustomDataConverter();
520
}
521
522
@Override
523
protected List<FrameworkMethod> computeTestMethods() {
524
List<FrameworkMethod> methods = super.computeTestMethods();
525
// Apply custom filtering or transformation
526
return methods.stream()
527
.filter(this::shouldIncludeMethod)
528
.collect(Collectors.toList());
529
}
530
531
private boolean shouldIncludeMethod(FrameworkMethod method) {
532
// Custom inclusion logic
533
return !method.getName().startsWith("skip");
534
}
535
}
536
```