Dynamic tests in JUnit 5 allow you to create tests at runtime, providing a flexible and powerful way to generate test cases dynamically. This feature is particularly useful when the number of test cases is not known beforehand or when you want to generate tests based on some runtime conditions.
Key Concepts
- Dynamic Tests: Tests that are generated at runtime.
- @TestFactory: Annotation used to indicate a method that generates dynamic tests.
- DynamicTest: A class representing a single dynamic test case.
- Stream, Collection, Iterable: Dynamic tests can be returned as a Stream, Collection, or Iterable.
Creating Dynamic Tests
Using @TestFactory
The @TestFactory
annotation is used to mark a method that generates dynamic tests. This method should return a Stream
, Collection
, or Iterable
of DynamicTest
instances.
Example
Here is a simple example of creating dynamic tests using @TestFactory
:
import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.TestFactory; import org.junit.jupiter.api.function.Executable; import java.util.Arrays; import java.util.Collection; import static org.junit.jupiter.api.Assertions.assertTrue; public class DynamicTestsExample { @TestFactory Collection<DynamicTest> dynamicTests() { return Arrays.asList( DynamicTest.dynamicTest("Test 1", () -> assertTrue(isEven(2))), DynamicTest.dynamicTest("Test 2", () -> assertTrue(isEven(4))), DynamicTest.dynamicTest("Test 3", () -> assertTrue(isEven(6))) ); } boolean isEven(int number) { return number % 2 == 0; } }
Explanation
- @TestFactory: Marks the method
dynamicTests
as a factory for dynamic tests. - DynamicTest.dynamicTest: Creates a new dynamic test with a display name and an executable (the test logic).
- assertTrue: Asserts that the condition is true.
Using Streams
Dynamic tests can also be generated using streams, which is useful for large or infinite sequences of tests.
import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.TestFactory; import java.util.stream.IntStream; import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertTrue; public class DynamicTestsStreamExample { @TestFactory Stream<DynamicTest> dynamicTestsFromStream() { return IntStream.range(0, 10) .mapToObj(n -> DynamicTest.dynamicTest("Test " + n, () -> assertTrue(isEven(n * 2)))); } boolean isEven(int number) { return number % 2 == 0; } }
Explanation
- IntStream.range(0, 10): Generates a stream of integers from 0 to 9.
- mapToObj: Maps each integer to a
DynamicTest
. - DynamicTest.dynamicTest: Creates a new dynamic test for each integer.
Practical Exercise
Exercise
Create a dynamic test that verifies if a list of strings contains only non-empty strings.
- Create a method
isNonEmpty
that checks if a string is non-empty. - Use
@TestFactory
to generate dynamic tests for a list of strings. - Use
assertTrue
to assert that each string is non-empty.
Solution
import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.TestFactory; import java.util.Arrays; import java.util.Collection; import static org.junit.jupiter.api.Assertions.assertTrue; public class NonEmptyStringTests { @TestFactory Collection<DynamicTest> dynamicTestsForNonEmptyStrings() { return Arrays.asList( DynamicTest.dynamicTest("Test 1", () -> assertTrue(isNonEmpty("Hello"))), DynamicTest.dynamicTest("Test 2", () -> assertTrue(isNonEmpty("JUnit"))), DynamicTest.dynamicTest("Test 3", () -> assertTrue(isNonEmpty("Dynamic Tests"))) ); } boolean isNonEmpty(String str) { return str != null && !str.isEmpty(); } }
Explanation
- isNonEmpty: Checks if a string is non-empty.
- dynamicTestsForNonEmptyStrings: Generates dynamic tests for a list of strings.
- assertTrue: Asserts that each string is non-empty.
Common Mistakes and Tips
- Returning Null: Ensure that the
@TestFactory
method does not returnnull
. It should return a non-nullStream
,Collection
, orIterable
. - Descriptive Names: Use descriptive names for dynamic tests to make it easier to identify which test failed.
- Avoiding Side Effects: Ensure that the test logic in dynamic tests does not have side effects that could affect other tests.
Conclusion
Dynamic tests in JUnit 5 provide a powerful way to generate tests at runtime, offering flexibility and scalability. By using @TestFactory
and DynamicTest
, you can create dynamic test cases based on runtime conditions, making your test suite more robust and adaptable.
JUnit Course
Module 1: Introduction to JUnit
Module 2: Basic JUnit Annotations
- Understanding @Test
- Using @Before and @After
- Using @BeforeClass and @AfterClass
- Ignoring Tests with @Ignore
Module 3: Assertions in JUnit
Module 4: Parameterized Tests
- Introduction to Parameterized Tests
- Creating Parameterized Tests
- Using @ParameterizedTest
- Custom Parameterized Tests
Module 5: Test Suites
Module 6: Mocking with JUnit
Module 7: Advanced JUnit Features
Module 8: Best Practices and Tips
- Writing Effective Tests
- Organizing Test Code
- Test-Driven Development (TDD)
- Continuous Integration with JUnit