In real-world applications, some scenarios require running the same test multiple times. For instance, stress testing, verifying flaky behavior, or ensuring deterministic outcomes across iterations. JUnit 5 makes this possible with the @RepeatedTest
annotation, eliminating the need for loops inside test methods.
In this tutorial, we’ll cover repeated tests in JUnit 5, explore use cases, dive into examples with RepetitionInfo
, and combine them with frameworks like Mockito and Testcontainers.
Why Repeated Tests Matter
- Reliability: Validate stability of flaky logic.
- Performance Testing: Run lightweight load simulations.
- Randomized Inputs: Ensure consistent results across multiple runs.
- Integration Testing: Verify container or API stability over several attempts.
- CI/CD Pipelines: Catch intermittent failures early.
Think of repeated tests like rehearsals for a play — by running the same scene multiple times, you catch mistakes before the premiere.
Basic Example: @RepeatedTest
JUnit 5 provides @RepeatedTest
for running a test multiple times.
import org.junit.jupiter.api.RepeatedTest;
import static org.junit.jupiter.api.Assertions.assertTrue;
class RepeatedTestExample {
@RepeatedTest(5)
void shouldRunFiveTimes() {
assertTrue(Math.random() >= 0); // Always true, runs 5 times
}
}
This test runs five times, with each repetition counted separately in reports.
Using RepetitionInfo for Context
JUnit 5 allows injecting RepetitionInfo
for more control.
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.RepetitionInfo;
class RepetitionInfoTest {
@RepeatedTest(3)
void testWithRepetitionInfo(RepetitionInfo info) {
System.out.println("Running repetition " + info.getCurrentRepetition() +
" of " + info.getTotalRepetitions());
}
}
Output:
Running repetition 1 of 3
Running repetition 2 of 3
Running repetition 3 of 3
Custom Display Names
You can customize how repetitions appear in test reports.
import org.junit.jupiter.api.RepeatedTest;
class CustomDisplayNameTest {
@RepeatedTest(value = 3, name = "Run {currentRepetition} of {totalRepetitions}")
void testWithCustomNames() {
System.out.println("Executing test...");
}
}
Report output:
Run 1 of 3 ✔
Run 2 of 3 ✔
Run 3 of 3 ✔
Combining Repeated Tests with Assertions
import org.junit.jupiter.api.RepeatedTest;
import static org.junit.jupiter.api.Assertions.assertEquals;
class CalculatorTest {
@RepeatedTest(5)
void shouldAddNumbersCorrectly() {
Calculator calc = new Calculator();
assertEquals(5, calc.add(2, 3));
}
}
Even though the logic doesn’t change, running it multiple times helps catch intermittent failures.
Repeated Tests with Mockito
import org.junit.jupiter.api.RepeatedTest;
import org.mockito.Mockito;
class OrderServiceTest {
@RepeatedTest(3)
void shouldVerifyMockMultipleTimes() {
OrderRepository repo = Mockito.mock(OrderRepository.class);
OrderService service = new OrderService(repo);
service.placeOrder(new Order("Book"));
Mockito.verify(repo).save(Mockito.any(Order.class));
}
}
Mockito ensures the expected behavior is validated in every repetition.
Repeated Tests with Testcontainers
Repeated tests are useful when testing container startup consistency.
import org.junit.jupiter.api.RepeatedTest;
import org.testcontainers.containers.PostgreSQLContainer;
class DatabaseContainerTest {
@RepeatedTest(2)
void shouldStartPostgresMultipleTimes() {
try (PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")) {
postgres.start();
System.out.println("Postgres running on: " + postgres.getJdbcUrl());
assert(postgres.isRunning());
}
}
}
Output (each repetition starts/stops a container):
Creating container for image: postgres:15
Container started in 11.5s
Real-World Scenarios
- Stress Testing APIs: Call endpoints multiple times to detect rate-limiting issues.
- Flaky Test Debugging: Run tests repeatedly to catch intermittent failures.
- Database Initialization: Validate Testcontainers setups across repetitions.
- Legacy Systems: Ensure consistency in fragile, unpredictable code.
Best Practices
- Use repeated tests strategically, not as a fix for flaky tests.
- Always log repetition details for debugging.
- Avoid excessive repetitions in CI pipelines (they increase build times).
- Combine with
@Tag
to separate fast vs slow repeated tests. - Use
RepetitionInfo
to apply conditional logic per iteration.
Version Tracker
- JUnit 4 → JUnit 5: Repeated tests introduced in JUnit 5 (
@RepeatedTest
). - Mockito Updates: Inline mocking improves reliability of repeated test scenarios.
- Testcontainers Growth: Expanded ecosystem makes repeated container tests viable.
Conclusion & Key Takeaways
Repeated tests in JUnit 5 provide a clean, built-in way to run tests multiple times. Whether for debugging flaky logic, testing containers, or stress-testing APIs, @RepeatedTest
adds flexibility to modern Java test suites.
Key Takeaways:
- Use
@RepeatedTest
for multiple executions. - Leverage
RepetitionInfo
for iteration context. - Combine with Mockito and Testcontainers for real-world scenarios.
- Apply strategically for debugging and validation.
FAQ
1. What is the difference between @Test and @RepeatedTest?@Test
runs once, while @RepeatedTest
executes multiple times.
2. Can I use @BeforeEach with repeated tests?
Yes, setup logic runs before each repetition.
3. How do I name repeated test executions?
Use the name
attribute with placeholders like {currentRepetition}
.
4. Do repeated tests slow down CI/CD pipelines?
Yes, use wisely to avoid long build times.
5. Can I repeat parameterized tests?
Yes, @RepeatedTest
works with parameter injection as well.
6. How are repeated tests reported in IDEs?
Each repetition shows as a separate execution in reports.
7. Should I use repeated tests for flaky test fixes?
No, fix root causes instead — use repetitions for debugging or stress testing.
8. Can I use assumptions in repeated tests?
Yes, assumptions apply per repetition.
9. Do repeated tests work with Testcontainers?
Yes, containers can be started/stopped per repetition.
10. Should I migrate to JUnit 5 for repeated tests?
Yes, @RepeatedTest
is a JUnit 5 feature not available in JUnit 4.