JUnit 5 @TestInstance(Lifecycle.PER_CLASS) Parallel Execution: Thread Safety Uncertainty
0 reputation · 16 Jun 2021, 18:01 UTC
0 reputation · 16 Jun 2021, 18:01 UTC
The @TestInstance(Lifecycle.PER_CLASS) annotation creates a single test class instance reused across all test methods. This differs from the default PER_METHOD lifecycle, which instantiates a fresh object for each test.
When parallel execution is enabled on the JUnit Platform, test methods may run concurrently. The documentation does not assert whether the single instance created by PER_CLASS is automatically serialized or if shared mutable state becomes a race condition.
Determine the behavior of PER_CLASS under parallel execution and the implications for thread safety and method ordering.
PER_CLASS when parallel execution is enabled?34775 reputation · 16 Jun 2021, 20:22 UTC
JUnit 5 does not automatically serialize test method execution for @TestInstance(Lifecycle.PER_CLASS) when parallel execution is enabled. The single test instance is shared across threads, so mutable state becomes a race condition unless you explicitly enforce serial execution or adopt a thread‑safe design.
junit.jupiter.execution.parallel.enabled=true.@Execution(ExecutionMode.SAME_THREAD) on the class or method.@BeforeAll and @AfterAll run once per class on a single thread, but @BeforeEach and @AfterEach may run concurrently with other test methods.@TestInstance annotation.@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class CounterTest {
int counter = 0;
@Test
void testA() { counter++; }
@Test
void testB() { counter++; }
}
junit.jupiter.execution.parallel.enabled=true to junit-platform.properties).counter in a @AfterAll method. A non‑deterministic value >2 indicates concurrent access.@Execution(ExecutionMode.SAME_THREAD) to the class and re‑run. The counter should reliably equal the number of test methods.final after @BeforeAll initialization, or use ThreadLocal for per‑thread data.@BeforeAll and never modify them thereafter.ThreadLocal<T> variable.synchronized or java.util.concurrent.locks.Lock.AtomicInteger, ConcurrentHashMap, etc., for shared mutable state.@Execution(ExecutionMode.SAME_THREAD) on the class or specific methods when isolation is critical.@TestInstance inherit the PER_CLASS instance, so the same concurrency rules apply.SpringExtension), check whether the extension imposes its own synchronization or lifecycle constraints.Which junit-jupiter version are you using? The parallel execution semantics changed between 5.3 and 5.9, so the exact behavior can vary with the version.
Use comments to ask for clarification. Post a solution as an answer.
34,775 reputation · 16 Jun 2021, 19:40 UTC
Adding @Execution(ExecutionMode.SAME_THREAD) to a @TestInstance(Lifecycle.PER_CLASS) class serializes that class’s methods on one thread, but it does not stop other test classes from running on separate threads when platform parallelism is enabled. Shared static fields or external resources across classes can still race.
Verification assumption: JUnit Platform 1.x. Log Thread.currentThread().getName() in @BeforeAll and @Test across two PER_CLASS classes with junit.jupiter.execution.parallel.enabled=true. Distinct thread names per class indicate cross-class concurrency even with SAME_THREAD.