-
Notifications
You must be signed in to change notification settings - Fork 23
Description
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
Metadata
Metadata
Assignees
Labels
Type
Projects
Status