CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-tngtech-java--junit-dataprovider

A TestNG-like dataprovider runner for JUnit having a simplified syntax compared to all the existing JUnit features.

Pending
Overview
Eval results
Files

custom-config.mddocs/

Custom Resolution and Configuration

This document covers advanced features including custom data provider method resolvers, test name formatting with placeholders, and filtering capabilities.

Required Imports

import com.tngtech.java.junit.dataprovider.DataProviderMethodResolver;
import com.tngtech.java.junit.dataprovider.internal.DefaultDataProviderMethodResolver;
import com.tngtech.java.junit.dataprovider.Placeholders;
import com.tngtech.java.junit.dataprovider.internal.placeholder.BasePlaceholder;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.TestClass;
import java.lang.reflect.Method;
import java.util.List;

Custom Data Provider Method Resolvers

The DataProviderMethodResolver interface allows you to implement custom logic for finding data provider methods.

public interface DataProviderMethodResolver {
    
    /**
     * Returns dataprovider methods for the given test method.
     * @param testMethod test method that uses a dataprovider
     * @param useDataProvider UseDataProvider annotation on the test method
     * @return resolved dataprovider methods or empty list if none found
     * @throws IllegalArgumentException if testMethod is null
     */
    List<FrameworkMethod> resolve(FrameworkMethod testMethod, UseDataProvider useDataProvider);
}

DefaultDataProviderMethodResolver

The built-in resolver that implements convention-based method resolution:

public class DefaultDataProviderMethodResolver implements DataProviderMethodResolver {
    
    /**
     * Resolve using convention-based strategies in this order:
     * 1. Explicit name via UseDataProvider.value()
     * 2. Method name equals test method name  
     * 3. Replace "test" prefix with "dataProvider" or "data"
     * 4. Add "dataProvider" or "data" prefix to capitalized method name
     * @param testMethod test method that uses a dataprovider
     * @param useDataProvider UseDataProvider annotation on the test method
     * @return resolved dataprovider methods or empty list if none found
     */
    public List<FrameworkMethod> resolve(FrameworkMethod testMethod, UseDataProvider useDataProvider)
    
    /**
     * Find classes where data provider methods should be searched.
     * @param testMethod test method context
     * @param locations specified location classes, or test class if empty
     * @return list of TestClass objects to search in
     */
    protected List<TestClass> findDataProviderLocations(FrameworkMethod testMethod, Class<?>[] locations)
    
    /**
     * Search for data provider methods across multiple locations.
     * @param locations TestClass objects to search in
     * @param testMethodName name of the test method
     * @param value explicit data provider name or DEFAULT_VALUE for convention
     * @return list of matching data provider methods
     */
    protected List<FrameworkMethod> findDataProviderMethods(List<TestClass> locations, String testMethodName, String value)
    
    /**
     * Find data provider method in a specific location using naming conventions.
     * @param location TestClass to search in
     * @param testMethodName name of the test method
     * @param value explicit data provider name or DEFAULT_VALUE for convention
     * @return matching data provider method or null if not found
     */
    protected FrameworkMethod findDataProviderMethod(TestClass location, String testMethodName, String value)
}

Custom Resolver Implementation

Create a custom resolver for specialized naming or lookup strategies:

public class PrefixBasedResolver implements DataProviderMethodResolver {
    
    @Override
    public List<FrameworkMethod> resolve(FrameworkMethod testMethod, UseDataProvider useDataProvider) {
        String testMethodName = testMethod.getName();
        String dataProviderName;
        
        if (!UseDataProvider.DEFAULT_VALUE.equals(useDataProvider.value())) {
            dataProviderName = useDataProvider.value();
        } else {
            // Custom logic: look for methods with "dp_" prefix
            dataProviderName = "dp_" + testMethodName;
        }
        
        TestClass testClass = new TestClass(testMethod.getMethod().getDeclaringClass());
        List<FrameworkMethod> dataProviderMethods = testClass.getAnnotatedMethods(DataProvider.class);
        
        for (FrameworkMethod method : dataProviderMethods) {
            if (method.getName().equals(dataProviderName)) {
                return Arrays.asList(method);
            }
        }
        
        return Collections.emptyList();
    }
}

// Usage
@Test
@UseDataProvider(resolver = PrefixBasedResolver.class)
public void testStringLength(String input, int expected) {
    assertEquals(expected, input.length());
}

@DataProvider
public static Object[][] dp_testStringLength() {  // Custom prefix
    return new Object[][] { {"hello", 5}, {"world", 5} };
}

Multiple Location Resolution

Use data providers from multiple classes:

public class CommonDataProviders {
    @DataProvider
    public static Object[][] commonStringData() {
        return new Object[][] { {"test", 4}, {"hello", 5} };
    }
}

public class SpecialDataProviders {
    @DataProvider  
    public static Object[][] specialStringData() {
        return new Object[][] { {"special", 7}, {"unique", 6} };
    }
}

@Test
@UseDataProvider(
    value = "commonStringData",
    location = { CommonDataProviders.class, SpecialDataProviders.class }
)
public void testWithMultipleLocations(String input, int length) {
    assertEquals(length, input.length());
}

Resolver Strategies

Control how multiple resolvers are used:

@Test
@UseDataProvider(
    resolver = { CustomResolver1.class, CustomResolver2.class },
    resolveStrategy = ResolveStrategy.UNTIL_FIRST_MATCH  // Default
)
public void testFirstMatch(Object data) {
    // Uses data from first resolver that finds a match
}

@Test
@UseDataProvider(
    resolver = { CustomResolver1.class, CustomResolver2.class },
    resolveStrategy = ResolveStrategy.AGGREGATE_ALL_MATCHES
)
public void testAggregated(Object data) {
    // Combines data from all resolvers that find matches
}

Test Method Name Formatting

Customize how parameterized test names are displayed using format patterns and placeholders.

Format Patterns

The @DataProvider annotation supports format patterns to control test method naming:

@DataProvider(format = "%m[%i: %p[0..-1]]")  // Default format
@DataProvider(format = "%m[%i]")             // Method name with index only
@DataProvider(format = "%m: %p[0]")          // Method name with first parameter
@DataProvider(format = "%c.%m(%p[0..1])")    // Class.method(first two params)

Available Placeholders

// Class placeholders
%c    // Simple class name (e.g., "DataProviderTest")
%cc   // Canonical class name (e.g., "com.example.DataProviderTest")

// Method placeholders  
%m    // Simple method name (e.g., "testStringLength")
%cm   // Complete method signature (e.g., "com.example.Test.testStringLength(java.lang.String)")

// Index placeholder
%i    // Data provider index (0, 1, 2, ...)

// Parameter placeholders
%p[0]      // First parameter
%p[-1]     // Last parameter  
%p[1..3]   // Parameters 1 through 3
%p[0..-2]  // All parameters except last
%p[0..-1]  // All parameters

Formatting Examples

@DataProvider(format = "%m[%i: %p[0]]")
public static Object[][] stringLengthData() {
    return new Object[][] {
        { "hello", 5 },
        { "world", 5 },
        { "", 0 }
    };
}
// Test names: testStringLength[0: hello], testStringLength[1: world], testStringLength[2: ]

@DataProvider(format = "%c.%m: %p[0] -> %p[1]")  
public static Object[][] conversionData() {
    return new Object[][] {
        { "123", 123 },
        { "true", true }
    };
}
// Test names: MyTest.testConversion: 123 -> 123, MyTest.testConversion: true -> true

@DataProvider(format = "%m(%p[0..1]) #%i")
public static Object[][] rangeData() {
    return new Object[][] {
        { 1, 10, "range1" },
        { 20, 30, "range2" }
    };
}
// Test names: testRange(1, 10) #0, testRange(20, 30) #1

Custom Placeholders

Extend the placeholder system with custom formatting logic.

Placeholders Utility Class

The Placeholders class manages the collection of placeholder implementations used for test method name formatting.

public class Placeholders {
    
    /**
     * Get all registered placeholders. Returns modifiable list.
     * @return all BasePlaceholder instances for format processing
     */
    public static List<BasePlaceholder> all()
    
    /**
     * Reset to default placeholders. Clears all custom placeholders and 
     * restores the default set: %c, %cc, %m, %cm, %i, %p[x]
     */
    public static void reset()
}

The default placeholders included are:

  • CanonicalClassNamePlaceholder (%cc) - Full canonical class name
  • CompleteMethodSignaturePlaceholder (%cm) - Complete method signature
  • IndexPlaceholder (%i) - Data provider row index
  • ParameterPlaceholder (%p[x]) - Parameter access with indexing
  • SimpleClassNamePlaceholder (%c) - Simple class name
  • SimpleMethodNamePlaceholder (%m) - Method name

Creating Custom Placeholders

Implement BasePlaceholder for custom formatting:

public abstract class BasePlaceholder {
    
    /**
     * Constructor requires placeholder regex pattern.
     * @param placeholderRegex regular expression to match placeholder
     */
    public BasePlaceholder(String placeholderRegex)
    
    /**
     * Set context for placeholder processing.
     * @param method test method being processed
     * @param idx data provider row index
     * @param parameters test method parameters
     */
    public void setContext(Method method, int idx, Object[] parameters)
    
    /**
     * Process format string, replacing placeholder occurrences.
     * @param formatPattern input format pattern
     * @return processed format string with replacements
     */
    public String process(String formatPattern)
    
    /**
     * Generate replacement for found placeholder. Override this method.
     * @param placeholder matched placeholder string
     * @return replacement string for the placeholder
     */
    protected abstract String getReplacementFor(String placeholder);
}

Example custom placeholder:

public class TimestampPlaceholder extends BasePlaceholder {
    private String timestamp;
    
    public TimestampPlaceholder() {
        super("%t");  // Regex pattern to match %t placeholder
    }
    
    @Override
    public void setContext(Method method, int idx, Object[] parameters) {
        this.timestamp = Instant.now().toString();
    }
    
    @Override
    protected String getReplacementFor(String placeholder) {
        return timestamp;
    }
}

// Register custom placeholder
static {
    Placeholders.all().add(0, new TimestampPlaceholder());
}

@DataProvider(format = "%m[%i] at %t")
public static Object[][] timestampedData() {
    return new Object[][] { {"test1"}, {"test2"} };
}
// Test names: testMethod[0] at 2023-12-01T10:30:00Z, testMethod[1] at 2023-12-01T10:30:00Z

Parameter Transformation Placeholder

public class HashPlaceholder extends BasePlaceholder {
    private Object[] parameters;
    
    public HashPlaceholder() {
        super("%hash");  // Regex pattern to match %hash placeholder
    }
    
    @Override
    public void setContext(Method method, int idx, Object[] parameters) {
        this.parameters = parameters;
    }
    
    @Override
    protected String getReplacementFor(String placeholder) {
        return String.valueOf(Arrays.hashCode(parameters));
    }
}

@DataProvider(format = "%m[%hash]")
public static Object[][] hashedData() {
    return new Object[][] { 
        {"param1", "param2"}, 
        {"param3", "param4"} 
    };
}
// Test names: testMethod[12345], testMethod[67890] (hash values)

Filtering and Test Selection

Control which tests run using JUnit filters enhanced for data provider support.

DataProviderFilter

public class DataProviderFilter extends Filter {
    
    /**
     * Create filter that supports data provider row filtering.
     * @param filter original filter to wrap
     */
    public DataProviderFilter(Filter filter)
    
    /**
     * Determine if test should run, supporting data provider syntax.
     * @param description test description
     * @return true if test should run
     */
    public boolean shouldRun(Description description)
    
    /**
     * Get description of wrapped filter.
     * @return filter description
     */
    public String describe()
    
    // Pattern constants for parsing test descriptions
    static final Pattern DESCRIPTION_PATTERN;
    static final Pattern GENEROUS_DESCRIPTION_PATTERN;
}

Filter Usage

The DataProviderRunner automatically wraps filters to support data provider test selection:

// These filtering patterns work with DataProviderFilter:
// "testMethod" - runs all data provider rows for testMethod
// "testMethod[0]" - runs only row 0 of testMethod data provider  
// "testMethod[0: param1, param2]" - runs specific parameterized test
// "com.example.TestClass" - runs all tests in TestClass

Programmatic Filtering

@RunWith(DataProviderRunner.class)
public class FilteredTest {
    
    @DataProvider
    public static Object[][] allData() {
        return new Object[][] {
            {"test1", 1}, {"test2", 2}, {"test3", 3}
        };
    }
    
    @Test
    @UseDataProvider("allData")
    public void testFiltered(String name, int value) {
        // Only specific rows will run based on filter
    }
}

// Run only second data row:
// mvn test -Dtest="FilteredTest#testFiltered[1]*"

Custom Filter Integration

Integrate with custom JUnit filters:

public class CustomTestFilter extends Filter {
    @Override
    public boolean shouldRun(Description description) {
        // Custom filtering logic
        return description.getDisplayName().contains("important");
    }
    
    @Override
    public String describe() {
        return "Custom filter for important tests";
    }
}

// DataProviderRunner automatically wraps custom filters:
runner.filter(new CustomTestFilter());
// Becomes: runner.filter(new DataProviderFilter(new CustomTestFilter()));

Integration with Test Frameworks

JUnit Categories

Data provider tests work seamlessly with JUnit Categories:

public interface SlowTests {}
public interface FastTests {}

@RunWith(DataProviderRunner.class)
@Category({FastTests.class})
public class CategorizedTest {
    
    @DataProvider
    public static Object[][] quickData() {
        return new Object[][] { {"quick1"}, {"quick2"} };
    }
    
    @Test
    @UseDataProvider("quickData")
    @Category(SlowTests.class)  // Override class-level category
    public void testCategorized(String input) {
        // Test logic
    }
}

Custom Runners and Extensions

Extend DataProviderRunner for specialized behavior:

public class CustomDataProviderRunner extends DataProviderRunner {
    
    public CustomDataProviderRunner(Class<?> clazz) throws InitializationError {
        super(clazz);
    }
    
    @Override
    protected void initializeHelpers() {
        super.initializeHelpers();
        // Customize data converter, test generator, or validator
        this.dataConverter = new CustomDataConverter();
    }
    
    @Override
    protected List<FrameworkMethod> computeTestMethods() {
        List<FrameworkMethod> methods = super.computeTestMethods();
        // Apply custom filtering or transformation
        return methods.stream()
                     .filter(this::shouldIncludeMethod)
                     .collect(Collectors.toList());
    }
    
    private boolean shouldIncludeMethod(FrameworkMethod method) {
        // Custom inclusion logic
        return !method.getName().startsWith("skip");
    }
}

Install with Tessl CLI

npx tessl i tessl/maven-com-tngtech-java--junit-dataprovider

docs

custom-config.md

data-formats.md

index.md

runner-annotations.md

utilities.md

tile.json