In modern applications, performance and responsiveness are just as important as correctness. A function that produces the right result but takes 30 seconds to execute might still be unacceptable. This is where timeout tests in JUnit 5 come into play. By setting strict execution limits, you can ensure your code runs within acceptable timeframes, prevent infinite loops from stalling builds, and maintain fast feedback in CI/CD pipelines.
In this tutorial, we’ll explore how to use @Timeout
, assertTimeout
, and assertTimeoutPreemptively
in JUnit 5 to handle long-running code effectively.
Why Timeout Tests Matter
- Catch Infinite Loops: Prevent builds from hanging indefinitely.
- Ensure Performance Contracts: Verify methods execute within defined SLAs.
- Stabilize CI/CD: Keep pipelines fast and reliable.
- Microservices Reliability: Ensure service calls don’t exceed response thresholds.
- Detect Flaky Behavior: Identify inconsistent execution times.
Think of timeouts as stopwatches for your tests — if the code runs too long, the test fails.
Using @Timeout Annotation
The @Timeout
annotation sets a maximum duration for test execution.
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import java.util.concurrent.TimeUnit;
class TimeoutAnnotationTest {
@Test
@Timeout(value = 2, unit = TimeUnit.SECONDS)
void testShouldFailIfExceedsTwoSeconds() throws InterruptedException {
Thread.sleep(3000); // This will fail after 2 seconds
}
}
Output:
org.opentest4j.TestAbortedException: execution timed out after 2 s
Using assertTimeout
assertTimeout
checks if a block of code finishes within the given duration.
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTimeout;
import java.time.Duration;
class TimeoutAssertionTest {
@Test
void testShouldCompleteWithinTime() {
assertTimeout(Duration.ofSeconds(2), () -> {
Thread.sleep(1000); // Passes, as it finishes within 2 seconds
});
}
}
Using assertTimeoutPreemptively
Unlike assertTimeout
, this method interrupts the code if it exceeds the limit.
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
import java.time.Duration;
class PreemptiveTimeoutTest {
@Test
void testShouldAbortImmediately() {
assertTimeoutPreemptively(Duration.ofSeconds(1), () -> {
Thread.sleep(2000); // Aborted after 1 second
});
}
}
⚠️ Be cautious:
assertTimeoutPreemptively
uses a separate thread and may cause issues with thread-local state.
Customizing Timeout Display Names
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.Timeout;
import java.util.concurrent.TimeUnit;
class TimeoutDisplayTest {
@RepeatedTest(3)
@Timeout(value = 1, unit = TimeUnit.SECONDS)
void repeatedTestWithTimeout() throws InterruptedException {
Thread.sleep(500); // Passes within 1 second
}
}
This ensures repeated executions all respect the timeout.
Real-World Scenarios
- REST API Calls: Ensure responses are received under SLA (e.g., 500ms).
- Database Queries: Validate that queries complete within expected limits.
- Message Brokers: Test Kafka/RabbitMQ interactions for responsiveness.
- Load Testing: Simulate repeated executions with time constraints.
- Flaky Tests: Apply timeouts to detect or skip unstable scenarios.
Timeout Tests with Mockito
Mockito can simulate slow behavior using thenAnswer
.
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import org.mockito.Mockito;
import static org.mockito.Mockito.*;
class MockitoTimeoutTest {
@Test
@Timeout(1)
void shouldFailDueToMockDelay() throws InterruptedException {
Service service = mock(Service.class);
when(service.fetchData()).thenAnswer(invocation -> {
Thread.sleep(2000); // Simulate slow response
return "data";
});
service.fetchData(); // Will exceed timeout
}
interface Service {
String fetchData();
}
}
Timeout Tests with Testcontainers
You can set timeouts to ensure container startup doesn’t stall builds.
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import org.testcontainers.containers.PostgreSQLContainer;
class TestcontainersTimeoutTest {
@Test
@Timeout(15)
void shouldStartContainerWithinTimeout() {
try (PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")) {
postgres.start();
System.out.println("Postgres running: " + postgres.getJdbcUrl());
}
}
}
Sample log:
Creating container for image: postgres:15
Container started in 10.9s
Best Practices
- Use reasonable timeouts — too strict can cause false failures, too loose reduces value.
- Prefer
assertTimeout
for clarity and predictability. - Use
assertTimeoutPreemptively
only when necessary. - Tag long-running tests (
@Tag("slow")
) and separate them in CI/CD. - Combine with
@RepeatedTest
to validate performance consistency.
Version Tracker
- JUnit 4 → JUnit 5: Timeouts moved from
@Test(timeout=…)
to@Timeout
andassertTimeout
. - Mockito Updates: Support for stubbing delays aligns with timeout testing.
- Testcontainers Growth: Ecosystem matured to handle startup constraints under timeouts.
Conclusion & Key Takeaways
Timeout tests in JUnit 5 provide a reliable way to enforce execution limits and catch performance bottlenecks. They ensure code is not just correct but also efficient, making applications production-ready.
Key Takeaways:
- Use
@Timeout
for simple per-test limits. - Use
assertTimeout
for scoped code blocks. - Use
assertTimeoutPreemptively
with caution. - Combine with Mockito, Testcontainers, and CI/CD for real-world validation.
FAQ
1. What is the difference between @Timeout and assertTimeout?@Timeout
applies to the entire test method, while assertTimeout
scopes to a specific code block.
2. Can I apply @Timeout at the class level?
Yes, it applies to all test methods in the class.
3. When should I use assertTimeoutPreemptively?
When you want to abort execution immediately after exceeding the limit.
4. Do timeouts work with parameterized tests?
Yes, they apply to each execution.
5. How do I debug flaky timeout tests?
Add logging and use RepetitionInfo
with @RepeatedTest
to reproduce inconsistencies.
6. Can I combine timeouts with Mockito?
Yes, mocks can simulate slow responses for testing.
7. Do CI/CD pipelines benefit from timeouts?
Yes, they prevent pipelines from stalling due to infinite loops or long waits.
8. What happens if I don’t set timeouts?
Long-running or stuck tests can freeze your test suite indefinitely.
9. Are timeouts supported in IDEs?
Yes, IntelliJ, Eclipse, and build tools fully support timeout annotations.
10. Should I migrate to JUnit 5 for timeouts?
Yes, JUnit 5 provides more flexible and robust timeout handling than JUnit 4.