JUnit – Dynamic Tests
JUnit dynamic tests are a powerful feature introduced in JUnit 5, allowing developers to generate test cases at runtime instead of defining them statically.
In this tutorial, you will learn how to create and use dynamic tests in JUnit using the @TestFactory
annotation, with step-by-step examples.
What Are Dynamic Tests?
Dynamic tests in JUnit are generated at runtime, allowing you to create multiple test cases programmatically. They are useful in situations where you cannot define all test cases at compile time.
For example:
- Testing data-driven scenarios where inputs are dynamically generated.
- Running tests with combinations of parameters.
- Validating logic against a set of conditions or constraints loaded from an external source (e.g., a database or file).
Regular test methods annotated with @Test
, whereas dynamic tests are created using the @TestFactory
annotation and a factory method that generates a stream, collection, or iterable of dynamic tests. This makes dynamic tests flexible, reusable, and ideal for scenarios where test data or conditions are not fixed.
How to Create Dynamic Tests
To create dynamic tests in JUnit, you use the @TestFactory
annotation. This annotation marks a method as a test factory, which generates a collection or stream of dynamic tests at runtime. Each dynamic test is represented as an instance of the DynamicTest
class.
Here’s the basic structure of a test factory method:
src/test/java/DynamicTestExample.java
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
import static org.junit.jupiter.api.Assertions.*;
import java.util.List;
class DynamicTestExample {
@TestFactory
List<DynamicTest> generateTests() {
return List.of(
DynamicTest.dynamicTest("Test 1", () -> assertEquals(4, 2 + 2)),
DynamicTest.dynamicTest("Test 2", () -> assertTrue(5 > 3))
);
}
}
In this example:
@TestFactory
: Marks the method as a test factory.DynamicTest.dynamicTest
: Creates a new dynamic test with a name and executable logic.- The method returns a list of dynamic tests, which JUnit executes at runtime.
Sources for Dynamic Test Generation
Dynamic tests can be generated from various sources, such as:
- Static Data: Predefined lists, arrays, or other collections.
- Dynamic Data: Data loaded from external sources like files, databases, or APIs.
- Combinatorial Logic: Tests generated from combinations of parameters.
Let’s explore each case in detail.
1 Generating Dynamic Tests from a List
In this example, dynamic tests are created for each item in a list of integers.
src/test/java/DynamicTestExample.java
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
import static org.junit.jupiter.api.Assertions.*;
import java.util.List;
class DynamicTestExample {
@TestFactory
List<DynamicTest> testFromList() {
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
return numbers.stream()
.map(num -> DynamicTest.dynamicTest("Test if " + num + " is positive",
() -> assertTrue(num > 0)))
.toList();
}
}
JUnit creates one test for each number in the list. Each test verifies that the number is positive.
2 Using External Data Sources for Dynamic Tests
You can load test data from external sources, such as files or databases. For example, using a CSV file:

/test-data.csv located in the root folder of the project.
Addition1,5,2,3
Addition2,7,3,4
Addition3,10,6,4
Addition4,15,10,5
src/test/java/DynamicTestExample.java
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.TestFactory;
import static org.junit.jupiter.api.Assertions.*;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Stream;
class DynamicTestExample {
@TestFactory
Stream<DynamicTest> testFromCsvFile() throws IOException {
List<String[]> data = Files.readAllLines(Path.of("test-data.csv"))
.stream()
.map(line -> line.split(","))
.toList();
return data.stream()
.map(row -> DynamicTest.dynamicTest("Test: " + row[0],
() -> assertEquals(Integer.parseInt(row[1]), Integer.parseInt(row[2]) + Integer.parseInt(row[3]))));
}
}
Here, each row in the CSV file represents a test case with inputs and expected results.
3 Testing Parameter Combinations using Dynamic Tests
Dynamic tests can be used to evaluate all combinations of parameters:
src/test/java/DynamicTestExample.java
@TestFactory
Stream<DynamicTest> testFromCombinations() {
List<String> names = List.of("Alice", "Bob");
List<Integer> ages = List.of(20, 30);
return names.stream()
.flatMap(name -> ages.stream()
.map(age -> DynamicTest.dynamicTest("Test for " + name + " age " + age,
() -> assertTrue(age > 0))));
}
Each combination of name and age generates a test case.
In the above program, we have (“Alice”, “Bob”) as names and (20, 30) for ages. The combinations would be (“Alice”, 20), (“Alice”, 30), (“Bob”, 20), and (“Bob”, 30). Therefore, four Tests would for the program, as shown in the following video.
Advantages of Dynamic Tests
- Flexibility: Generate tests based on dynamic conditions or external data.
- Reusability: Avoid duplicating test methods for different inputs.
- Scalability: Easily add or modify test cases by updating the data source.
- Debugging: Each dynamic test has a unique name, making it easier to identify failing cases.
Best Practices for Dynamic Tests
- Meaningful Names: Use descriptive names for dynamic tests to make debugging easier.
- Organized Data: Maintain test data in structured formats like CSV, JSON, or databases.
- Focus on Logic: Ensure dynamic tests focus on validating the logic rather than covering trivial cases.
- Test Coverage: Use dynamic tests to increase coverage for edge cases and parameter combinations.
Conclusion
By using @TestFactory
, you can create JUnit dynamic tests that are reusable, scalable, and efficient tests that adapt to a variety of conditions.