Skip to content

SpringUIUnitTest Parallel Execution Issue #1990

@Artur-

Description

@Artur-

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

No one assigned

    Type

    No type

    Projects

    Status

    Maybe

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions