SpringUIUnitTest Parallel Execution Issue
Summary
SpringUIUnitTest fails when test classes are executed in parallel due to bean registration conflicts in the shared Spring application context.
Environment
- Vaadin: 24.8.7
- Spring Boot: 3.5.5
- JUnit 5: 5.11.3
- Java: 21
Problem Description
When running multiple test classes that extend SpringUIUnitTest in parallel, tests fail with:
java.lang.IllegalStateException: Could not register object [com.vaadin.testbench.unit.mocks.SpringSecurityRequestCustomizer@XXX]
under bean name 'com.vaadin.testbench.unit.mocks.SpringSecurityRequestCustomizer':
there is already object [com.vaadin.testbench.unit.mocks.SpringSecurityRequestCustomizer@YYY] bound
Root Cause
The UITestSpringLookupInitializer class registers beans as singletons in the Spring context. When multiple test classes run in parallel and share the same Spring context, they attempt to register the same bean multiple times, causing conflicts.
Minimal Reproducible Example
https://github.com/Artur-/springuitest-parallel-issue
Expected Behavior
Tests should run successfully in parallel without bean registration conflicts.
Actual Behavior
Tests fail with:
java.lang.IllegalStateException: Could not register object [com.vaadin.testbench.unit.mocks.SpringSecurityRequestCustomizer@1d76957d]
under bean name 'com.vaadin.testbench.unit.mocks.SpringSecurityRequestCustomizer':
there is already object [com.vaadin.testbench.unit.mocks.SpringSecurityRequestCustomizer@27fa67ee] bound
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.addSingleton(DefaultSingletonBeanRegistry.java:162)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.addSingleton(DefaultListableBeanFactory.java:1460)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.registerSingleton(DefaultSingletonBeanRegistry.java:146)
at com.vaadin.testbench.unit.UITestSpringLookupInitializer.beforeTestMethod(UITestSpringLookupInitializer.java:56)
Analysis
The issue occurs in UITestSpringLookupInitializer.beforeTestMethod() which registers beans without checking if they already exist:
// Problematic code in UITestSpringLookupInitializer
@Override
public void beforeTestMethod(TestContext testContext) {
ConfigurableApplicationContext context = (ConfigurableApplicationContext) testContext.getApplicationContext();
ConfigurableListableBeanFactory factory = context.getBeanFactory();
// This fails when multiple threads try to register the same bean
factory.registerSingleton(SpringSecurityRequestCustomizer.class.getName(),
new SpringSecurityRequestCustomizer());
// ... other bean registrations
}
Workarounds
Workaround 1: Disable Parallel Execution
# junit-platform.properties
junit.jupiter.execution.parallel.enabled=false
Workaround 2: Use @DirtiesContext (Slow)
@SpringBootTest
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public abstract class BaseUITest extends SpringUIUnitTest {
// Each test class gets its own context, but this is very slow
}
Workaround 3: Use @isolated Annotation
@Isolated // Forces sequential execution for this test class
public class CriticalUITest extends BaseUITest {
// Tests run sequentially
}
Suggested Fix
The UITestSpringLookupInitializer should check if beans are already registered before attempting to register them:
@Override
public void beforeTestMethod(TestContext testContext) {
ConfigurableApplicationContext context = (ConfigurableApplicationContext) testContext.getApplicationContext();
ConfigurableListableBeanFactory factory = context.getBeanFactory();
String beanName = SpringSecurityRequestCustomizer.class.getName();
// Check if bean is already registered
if (!factory.containsSingleton(beanName)) {
synchronized (factory) {
// Double-check inside synchronized block
if (!factory.containsSingleton(beanName)) {
factory.registerSingleton(beanName, new SpringSecurityRequestCustomizer());
}
}
}
// ... similar pattern for other beans
}
Or alternatively, use prototype scope or unique bean names per test:
String beanName = SpringSecurityRequestCustomizer.class.getName() + "_" + testContext.getTestClass().getName();
factory.registerSingleton(beanName, new SpringSecurityRequestCustomizer());
Impact
- Performance: Forces users to run tests sequentially, significantly increasing test execution time
- CI/CD: Longer build times in continuous integration pipelines
- Developer Experience: Slower feedback loop during development
- Scalability: Cannot leverage multi-core processors for test execution
Additional Notes
- This issue affects all projects using SpringUIUnitTest with parallel test execution
- The problem becomes more pronounced as the test suite grows
- Modern CI environments encourage parallel execution for faster builds
References
SpringUIUnitTest Parallel Execution Issue
Summary
SpringUIUnitTest fails when test classes are executed in parallel due to bean registration conflicts in the shared Spring application context.
Environment
Problem Description
When running multiple test classes that extend SpringUIUnitTest in parallel, tests fail with:
Root Cause
The
UITestSpringLookupInitializerclass registers beans as singletons in the Spring context. When multiple test classes run in parallel and share the same Spring context, they attempt to register the same bean multiple times, causing conflicts.Minimal Reproducible Example
https://github.com/Artur-/springuitest-parallel-issue
Expected Behavior
Tests should run successfully in parallel without bean registration conflicts.
Actual Behavior
Tests fail with:
Analysis
The issue occurs in
UITestSpringLookupInitializer.beforeTestMethod()which registers beans without checking if they already exist:Workarounds
Workaround 1: Disable Parallel Execution
Workaround 2: Use @DirtiesContext (Slow)
Workaround 3: Use @isolated Annotation
Suggested Fix
The
UITestSpringLookupInitializershould check if beans are already registered before attempting to register them:Or alternatively, use prototype scope or unique bean names per test:
Impact
Additional Notes
References