Basic Usage
This guide covers the fundamental concepts of TableTest: table structure, value formats, and execution model.
Table Structure
A TableTest table is a pipe-delimited text block. Columns are separated by pipes (|), and each line is a row. Pipes are only used between columns — not at the start or end of a line.
@TableTest("""
Column1 | Column2 | Column3
value1 | value2 | value3
value4 | value5 | value6
""")Header Row
The first row defines column names. Columns map to method parameters by order, so you can use readable header names with whitespace and punctuation:
@TableTest("""
Input Value | Result?
5 | 10
""")
void headerRow(int input, int expected) {
assertEquals(expected, input * 2);
}Data Rows
Each data row represents one test execution. A table with 3 data rows produces 3 test executions.
@TableTest("""
Value | Result
1 | 2
3 | 6
5 | 10
""")
void testDouble(int value, int result) {
assertEquals(result, value * 2);
}Scenario Column
If there is one additional column than parameters, TableTest will use the leftmost column as the scenario column. Scenario values appear as test display names in reports and are not passed to the method:
@TableTest("""
Scenario | Input Value | Result?
Doubles | 5 | 10
""")
void headerRowScenario(int input, int expected) {
assertEquals(expected, input * 2);
}See Scenario Names for explicit scenario columns and other options.
Execution Model
TableTests are JUnit parameterized tests under the hood. They follow the standard JUnit execution model.
One Execution Per Row
Each data row triggers one method invocation:
@TableTest("""
Scenario | Value
First | 1
Second | 2
Third | 3
""")
void oneExecutionPerRow(int value) {
System.out.println("Value: " + value);
}Independent Executions
Each execution is independent. State doesn’t carry over between rows:
private int counter = 0;
@TableTest("""
Scenario | Value
First | 1
Second | 2
""")
void independentExecutions(int value) {
assertEquals(1, ++counter); // counter will initialise to 0 for each row
}Best practice: Keep tests stateless. Each row should be independently verifiable.
Test Lifecycle
For each row:
- TableTest reads the row values
- Converts values to parameter types
- Asks JUnit to run the test method with those parameters
JUnit lifecycle methods (@BeforeEach, @AfterEach) run for each row.
Value Formats
TableTest supports four value formats: single values, lists, sets, and maps. These can be nested to create complex data structures.
Single Values
Single values are converted to the corresponding parameter type — primitives, strings, enums, dates, and other types supported by JUnit’s built-in converters.
Values can appear with or without quotes. Surrounding single (') or double (") quotes are required when the value contains characters that could be confused with table syntax (such as |, [, or {). Whitespace around unquoted values is trimmed. To preserve leading or trailing whitespace, use quotes.
Empty values are represented by adjacent quote pairs ("" or '').
@TableTest("""
Value | Length?
Hello, world! | 13
"cat file.txt | wc -l" | 20
"[]" | 2
'' | 0
""")
void testString(String value, int expectedLength) {
assertEquals(expectedLength, value.length());
}When single values appear as elements inside collections (lists, sets, or maps), collection syntax characters also require quoting.
Lists
Lists convert to List or array parameter types. Lists are enclosed in square brackets with comma-separated elements. Lists can contain single values or compound values (nested lists, sets, or maps). Empty lists are represented by [].
@TableTest("""
List | Size? | Sum?
[] | 0 | 0
[1] | 1 | 1
[3, 2, 1] | 3 | 6
""")
void integerList(List<Integer> list, int expectedSize, int expectedSum) {
assertEquals(expectedSize, list.size());
assertEquals(expectedSum, list.stream().mapToInt(Integer::intValue).sum());
}Sets
Sets convert to Set parameter types. When the parameter is not a Set, curly braces denote a value set instead — each element becomes a separate test invocation. Sets are enclosed in curly braces with comma-separated elements. Sets can contain single values or compound values. Empty sets are represented by {}.
@TableTest("""
Set | Size?
{1, 2, 3} | 3
{Hello} | 1
{} | 0
""")
void testSet(Set<String> set, int expectedSize) {
assertEquals(expectedSize, set.size());
}Maps
Maps convert to Map parameter types. Maps use square brackets with comma-separated key-value pairs, where colons separate keys and values. Keys must be unquoted single values without table or collection syntax characters. Values can be single (unquoted or quoted) or compound (list, set, or map). Empty maps are represented by [:].
@TableTest("""
Map | Size?
[one: 1, two: 2, three: 3] | 3
[:] | 0
""")
void testMap(Map<String, Integer> map, int expectedSize) {
assertEquals(expectedSize, map.size());
}Nested Structures
Lists, sets, and maps can be nested to create complex data structures. TableTest converts nested values recursively using generic type information from the test method parameter.
@TableTest("""
Scores | Top scorer?
[Alice: [90, 80], Bob: [70, 60]] | Alice
[Alice: [75, 85], Bob: [95, 90]] | Bob
""")
void testNestedParameterizedTypes(
Map<String, List<Integer>> scores,
String expectedTopScorer
) {
assertEquals(expectedTopScorer, topScorer(scores));
}Null Values
A blank cell represents the absence of a value — there is no null keyword. Simply leave the cell empty.
@TableTest("""
String | Integer | List | Map | Set
| | | |
""")
void blankConvertsToNull(
String string, Integer integer, List<?> list,
Map<String, ?> map, Set<?> set
) {
assertNull(string);
assertNull(integer);
assertNull(list);
assertNull(map);
assertNull(set);
}A blank cell converts to null for all object types, including String, wrapper types, and object arrays. Primitive types (int, boolean, etc.) and primitive arrays (int[], etc.) cannot represent null — use their wrapper equivalents (Integer, Boolean[]) when a parameter may be blank.
Next Steps
- Type Conversion — Built-in and custom
@TypeConvertermethods - Advanced Features — Scenario names, value sets, external files, JUnit-supplied parameters