Skip to content
🎉 TableTest 1.2.0 is out — array parameter support and Java 8+. Read the changelog.
Type Conversion

Type Conversion

TableTest converts values from table cells to the types required by test method parameters. Understanding the conversion strategy helps you work with custom types and complex data effectively.

Conversion Strategy

TableTest uses a two-tier conversion approach:

  1. Custom converter methods — annotated with @TypeConverter in your test class or external sources
  2. JUnit built-in converters — standard type conversion as a fallback

Custom converters take priority, allowing you to override built-in conversion when needed.

Built-in Conversion

Single Values

The parameter type controls conversion. A cell value like 42 converts to int, long, double, or any other numeric type depending on what the parameter declares. The commonly-used built-in conversions include:

  • Numbersint, long, short, byte, float, double, and their wrapper types. The cell value is parsed according to the target type.
  • Boolean and chartrue/false for booleans, single characters for char.
  • Enums — Converted by name (e.g. MONDAY for DayOfWeek).
  • Time typesLocalDate, LocalDateTime, LocalTime, Instant, Duration, Period, and other java.time types, parsed in their standard ISO formats.
  • Paths and URIsPath, File, URI, and URL.

See the JUnit documentation for the full list.

@TableTest("""
    Number | Text | Date       | Class
    1      | abc  | 2025-01-20 | java.lang.Integer
    """)
void singleValues(short number, String text, LocalDate date, Class<?> type) {
    // All converted automatically
}

Collections and Arrays

When the parameter is a parameterised type like List<Integer> or Map<String, LocalDate>, TableTest uses the generic type information to convert each element individually. Map keys remain String type and are not converted.

@TableTest("""
    Grades                                       | Highest Grade?
    [Alice: [95, 87, 92], Bob: [78, 85, 90]]     | 95
    [Charlie: [98, 89, 91], David: [45, 60, 70]] | 98
    """)
void testParameterizedTypes(Map<String, List<Integer>> grades,
                            int expectedHighestGrade) {
    assertEquals(expectedHighestGrade, highestGrade(grades));
}

The same [a, b, c] list syntax also works for array parameters — both object arrays (String[]) and primitive arrays (int[]). The parameter type determines the result.

@TableTest("""
    Numbers   | Sum?
    [1, 2, 3] | 6
    [10, 20]  | 30
    []        | 0
    """)
void sumArray(int[] numbers, int expectedSum) {
    assertEquals(expectedSum, IntStream.of(numbers).sum());
}

Custom Converter Methods

When you need to convert to a type that isn’t covered by built-in conversion, define a custom converter method annotated with @TypeConverter.

record Discount(int percentage) {}

@TypeConverter
public static Discount parseDiscount(String input) {
    String digits = input.replace("%", "").trim();
    return new Discount(Integer.parseInt(digits));
}

Then use it in a test:

@TableTest("""
    Purchases | Discount?
    5         | 10%
    15        | 20%
    40        | 40%
    """)
void testDiscount(int purchases, Discount discount) {
    assertEquals(discount, calculateDiscount(purchases));
}

Custom converters also work with collections — TableTest applies the converter to each element:

@TableTest("""
    Discounts       | Best?
    [10%, 20%, 30%] | 30%
    [5%, 15%]       | 15%
    """)
void testCollectionsWithCustomTypes(List<Discount> discounts, Discount best) {
    assertEquals(best, bestDiscount(discounts));
}

Converter Method Rules

A valid custom converter method must:

  1. Be annotated with @TypeConverter
  2. Be a public static method in a public class (see Kotlin converters below)
  3. Accept exactly one parameter
  4. Return an object of the target parameter type
  5. Be the only @TypeConverter method matching the above criteria in the class

There is no specific naming pattern — any method fulfilling these requirements will be used.

Kotlin Converters

In Kotlin, @TypeConverter methods require @JvmStatic and can be declared in two locations:

  1. In the companion object of the test class (as shown in the examples above)
  2. At package level in the file containing the test class

Converter Parameter Types

The converter parameter doesn’t have to be String. It can be any type that TableTest knows how to convert. When the converter’s parameter type doesn’t match the parsed value, TableTest recursively attempts to convert the value to match — using custom converters or built-in conversion as needed. In the example below, the converter accepts a double — TableTest first converts the cell value from string to double using built-in conversion, then passes the result to fromCelsius.

@TypeConverter
public static Temperature fromCelsius(double celsius) {
    return new Temperature(celsius);
}
@TableTest("""
    Celsius | Fahrenheit?
    0.0     | 32.0
    100.0   | 212.0
    """)
void testTemperature(Temperature celsius, double fahrenheit) {
    assertEquals(fahrenheit, celsius.toFahrenheit(), 0.01);
}

Overriding Built-in Conversion

Since custom converters take priority over built-in conversion, you can override the built-in conversion for specific types:

@TypeConverter
public static LocalDate parseLocalDate(String input) {
    return switch (input) {
        case "yesterday" -> LocalDate.parse("2025-06-06");
        case "today" -> LocalDate.parse("2025-06-07");
        case "tomorrow" -> LocalDate.parse("2025-06-08");
        default -> LocalDate.parse(input);
    };
}
@TableTest("""
    This Date  | Other Date | Is Before?
    today      | tomorrow   | true
    today      | yesterday  | false
    2024-02-29 | 2024-03-01 | true
    """)
void testIsBefore(LocalDate thisDate, LocalDate otherDate, boolean expectedIsBefore) {
    assertEquals(expectedIsBefore, thisDate.isBefore(otherDate));
}

JUnit Explicit Argument Conversion

JUnit’s explicit argument conversion with @ConvertWith can also be used with TableTest methods. Note that the ArgumentConverter receives the parsed value (not the raw string).

Custom Converter Sources

To reuse converter methods across test classes, use the @TypeConverterSources annotation to list classes containing converters:

@TypeConverterSources({SharedConverters.class, MoreConverters.class})
public class ExampleTest {
    // TableTest methods can use converters from the listed classes
}

A converter source class is a regular class with public static @TypeConverter methods. In Kotlin, use an object declaration with @JvmStatic:

public class SharedConverters {
    @TypeConverter
    public static Discount toDiscount(double rate) {
        return new Discount(rate / 100);
    }
}

Converter Search Order

TableTest searches for a matching custom converter in the test class first, then in classes listed by @TypeConverterSources, and stops at the first match. This means a converter defined in the test class always takes precedence over one in a shared source.

Next Steps

Explore value sets, external table files, and more: Advanced Features