1. Overview
In this short tutorial, we'll use JUnit5's @Timeout annotation to set timeouts for unit tests in a declarative style. We'll discuss different ways of using it, and then we'll see how it interacts with @Pratemerized and @Nested tests.
2. The @Timeout Annotation
We can annotate a unit test with JUnit5's @Timeout to specify the maximum number of seconds it can run for; if this value is exceeded, the test will fail with a java.util.concurrent.TimeoutException:
@Test
@Timeout(1)
void shouldFailAfterOneSecond() throws InterruptedException {
Thread.sleep(10_000);
}
2.1. The Value and Unit Attributes
We've learned how to specify a test's timeout by specifying the number of seconds after which it fails. However, we can leverage the value and unit attributes of the annotation to specify different units of measurement:
@Test
@Timeout(value = 2, unit = TimeUnit.MINUTES)
void shouldFailAfterTwoMinutes() throws InterruptedException {
Thread.sleep(10_000);
}
2.2. The ThreadMode Attribute
Let's assume we have a slow test and, therefore, a large timeout. To run this efficiently, we should run this test on a different thread, not blocking the other tests. To achieve this, we can use JUnit5's parallel test execution.
On the other hand, the @Timeout annotation itself allows us to do it elegantly through its threadMode attribute:
@Test
@Timeout(value = 5, unit = TimeUnit.MINUTES, threadMode = Timeout.ThreadMode.SEPARATE_THREAD)
void shouldUseADifferentThread() throws InterruptedException {
System.out.println(Thread.currentThread().getName());
Thread.sleep(10_000);
}
We can check this by running the test and printing the current thread's name; it should print something like “junit-timeout-thread-1“.
3. @Timeout‘s Target
As highlighted earlier, the @Timeout annotation can be conveniently applied to individual test methods. However, it's also possible to specify a default timeout duration for each test from the entire class by placing the annotation at the class level. As a result, the tests that do not override the class-level timeout will fail if that value is exceeded:
@Timeout(5)
class TimeoutUnitTest {
@Test
@Timeout(1)
void shouldFailAfterOneSecond() throws InterruptedException {
Thread.sleep(10_000);
}
@Test
void shouldFailAfterDefaultTimeoutOfFiveSeconds() throws InterruptedException {
Thread.sleep(10_000);
}
}
3.1. @Timeout and @Nested Tests
JUnit5's @Nested annotation can create inner classes for unit tests. We can use this in combination with @Timeout. If the parent class defines a default timeout value, it will be used by the test from the inner class as well:
@Timeout(5)
class TimeoutUnitTest {
@Nested
class NestedClassWithoutTimeout {
@Test
void shouldFailAfterParentsDefaultTimeoutOfFiveSeconds() throws InterruptedException {
Thread.sleep(10_000);
}
}
}
However, this value can be overridden either at the nested class level or the method level:
@Nested
@Timeout(3)
class NestedClassWithTimeout {
@Test
void shouldFailAfterNestedClassTimeoutOfThreeSeconds() throws InterruptedException {
Thread.sleep(10_000);
}
@Test
@Timeout(1)
void shouldFailAfterOneSecond() throws InterruptedException {
Thread.sleep(10_000);
}
}
3.2. @Timeout and @ParameterizedTest
We can leverage the @ParameterizedTest annotation to execute multiple tests based on a given set of input values. We can annotate a parameterized test with @Timeout, and, as a result, each generated test will use the timeout value.
For instance, if we have a @ParameterizedTest that will execute five tests and we annotate it with @Timeout(1), each of the five resulting tests will fail if it exceeds one second:
@Timeout(1)
@ParameterizedTest
@ValueSource(ints = {1, 2, 3, 4, 5})
void eachTestShouldFailAfterOneSecond(int input) throws InterruptedException {
Thread.sleep(1100);
}
4. Conclusion
In this article, we discussed JUnit5's new @Timeout annotation. We learned how to configure it and use it to set timeout values for our unit test in a declarative manner.
As always, the full source code for this article is available over on GitHub.
res – Junit (guide) (cat=Testing)