We noticed that our tests which use @SpringBootTest run much slower after upgrading to Spring Boot 4.0.
We noticed that the application context is reused between tests from a single test class.
But the application context is paused and restarted between test classes while all test classes are using the same context. This is enforced by extending an abstract class which contains the configuration.
We enabled the DEBUG level to make sure only 1 context is in the cache during the build:
Spring test ApplicationContext cache statistics: [DefaultContextCache@1edd726c size = 1, maxSize = 32, contextUsageCount = 1, parentContextCount = 0, hitCount = 48, missCount = 1, failureCount = 0]
We noticed that SpringExtension.afterAll will call testContextManager.afterTestClass(). This will mark the context as unused and as a consequence will pause the context. When the new test runs it will fetch the same context from the cache and restart it.
This is the stack trace causing the context to be paused:
at org.springframework.test.context.cache.DefaultContextCache.unregisterContextUsage(DefaultContextCache.java:226)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.unregisterContextUsage(DefaultCacheAwareContextLoaderDelegate.java:225)
at org.springframework.test.context.support.DefaultTestContext.markApplicationContextUnused(DefaultTestContext.java:155)
at org.springframework.test.context.TestContextManager.afterTestClass(TestContextManager.java:559)
at org.springframework.test.context.junit.jupiter.SpringExtension.afterAll(SpringExtension.java:183)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor$$Lambda/0x00000fc002f0bc00.invoke(Unknown Source:-1)
at org.junit.jupiter.engine.descriptor.CallbackSupport.lambda$invokeAfterCallbacks$1(CallbackSupport.java:49)
at org.junit.jupiter.engine.descriptor.CallbackSupport$$Lambda/0x00000fc002eeec00.execute(Unknown Source:-1)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:74)
at org.junit.jupiter.engine.descriptor.CallbackSupport.lambda$invokeAfterCallbacks$0(CallbackSupport.java:49)
at org.junit.jupiter.engine.descriptor.CallbackSupport$$Lambda/0x00000fc002eee800.accept(Unknown Source:-1)
at org.junit.platform.commons.util.CollectionUtils.forEachInReverseOrder(CollectionUtils.java:213)
at org.junit.jupiter.engine.descriptor.CallbackSupport.invokeAfterCallbacks(CallbackSupport.java:48)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.invokeAfterAllCallbacks(ClassBasedTestDescriptor.java:493)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.after(ClassBasedTestDescriptor.java:271)
at org.junit.jupiter.engine.descriptor.ClassBasedTestDescriptor.after(ClassBasedTestDescriptor.java:88)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:186)
at org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda/0x00000fc002effc00.execute(Unknown Source:-1)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:74)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$1(NodeTestTask.java:186)
at org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda/0x00000fc001324c00.invoke(Unknown Source:-1)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:138)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$0(NodeTestTask.java:164)
at org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda/0x00000fc001324800.execute(Unknown Source:-1)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:74)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:163)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:116)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService$$Lambda/0x00000fc001326400.accept(Unknown Source:-1)
at java.util.ArrayList.forEach(ArrayList.java:1604)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:42)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$2(NodeTestTask.java:180)
at org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda/0x00000fc001325000.execute(Unknown Source:-1)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:74)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$1(NodeTestTask.java:166)
at org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda/0x00000fc001324c00.invoke(Unknown Source:-1)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:138)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$0(NodeTestTask.java:164)
at org.junit.platform.engine.support.hierarchical.NodeTestTask$$Lambda/0x00000fc001324800.execute(Unknown Source:-1)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:74)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:163)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:116)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:36)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:52)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:58)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.executeEngine(EngineExecutionOrchestrator.java:246)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.failOrExecuteEngine(EngineExecutionOrchestrator.java:218)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:179)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:66)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator$$Lambda/0x00000fc001305c00.accept(Unknown Source:-1)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:157)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:65)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:125)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:93)
at org.junit.platform.launcher.core.DelegatingLauncher.execute(DelegatingLauncher.java:48)
at org.junit.platform.launcher.core.InterceptingLauncher.lambda$execute$0(InterceptingLauncher.java:41)
at org.junit.platform.launcher.core.InterceptingLauncher$$Lambda/0x00000fc001229000.proceed(Unknown Source:-1)
at org.junit.platform.launcher.core.ClasspathAlignmentCheckingLauncherInterceptor.intercept(ClasspathAlignmentCheckingLauncherInterceptor.java:25)
at org.junit.platform.launcher.core.InterceptingLauncher.execute(InterceptingLauncher.java:40)
at org.junit.platform.launcher.core.DelegatingLauncher.execute(DelegatingLauncher.java:48)
at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:135)
at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:110)
at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:104)
at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:64)
at java.lang.invoke.LambdaForm$DMH/0x00000fc0011ec000.invokeInterface(LambdaForm$DMH:-1)
at java.lang.invoke.LambdaForm$MH/0x00000fc0011ec800.invoke(LambdaForm$MH:-1)
at java.lang.invoke.Invokers$Holder.invokeExact_MT(Invokers$Holder:-1)
at jdk.internal.reflect.DirectMethodHandleAccessor.invokeImpl(DirectMethodHandleAccessor.java:154)
at jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.lang.reflect.Method.invoke(Method.java:565)
at org.gradle.internal.dispatch.MethodInvocation.invokeOn(MethodInvocation.java:77)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:28)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:19)
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:88)
at jdk.proxy2.$Proxy6.stop(Unknown Source:-1)
at org.gradle.api.internal.tasks.testing.worker.TestWorker$3.run(TestWorker.java:194)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:126)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:103)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:63)
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:122)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:72)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)