Craig Otis
Craig Otis

Reputation: 32104

Spring JUnit test not properly reusing ApplicationContext

I have a simple JUnit test, based on the AbstractJUnit4SpringContextTests class:

@ContextConfiguration(locations = {
        "/test-spring-config.xml", "/test-databaseApplicationContext.xml",
        "/test-sharedApplicationContext.xml", "/test-dispatcher-servlet.xml"
})
public class TestTest extends AbstractJUnit4SpringContextTests {

    @Test
    public void testOneThing() {

    }
}

And a bean that is loaded as part of the application context:

<bean id="mySingleton" class="com.company.SingletonClass" />

This Singleton, as it is used in other projects/locations, has a constructor that ensures there is only a single instance of the class at a given time:

public class SingletonClass {
    private static SingletonClass instance = null;
    public SingletonClass() {
        if (instance != null) {
            throw new IllegalStateException("Highlander rules in effect.");
        }
        instance = this;
    }
}

However, when I run my JUnit test, this exception is hit. I understood that from this answer: Reuse spring application context across junit test classes

The application context was reused as long as the locations are the same. However, this doesn't seem to be the case. These are the two locations in which the bean is instantiated:

First:

Thread [main] (Suspended (breakpoint at line 47 in SingletonClass)) 
    SingletonClass.<init>() line: 47    
    NativeConstructorAccessorImpl.newInstance0(Constructor, Object[]) line: not available [native method]   
    NativeConstructorAccessorImpl.newInstance(Object[]) line: 57    
    DelegatingConstructorAccessorImpl.newInstance(Object[]) line: 45    
    Constructor<T>.newInstance(Object...) line: 525 
    BeanUtils.instantiateClass(Constructor<T>, Object...) line: 147 
    CglibSubclassingInstantiationStrategy(SimpleInstantiationStrategy).instantiate(RootBeanDefinition, String, BeanFactory) line: 76    
    DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).instantiateBean(String, RootBeanDefinition) line: 990    
    DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).createBeanInstance(String, RootBeanDefinition, Object[]) line: 943   
    DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).doCreateBean(String, RootBeanDefinition, Object[]) line: 485 
    DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).createBean(String, RootBeanDefinition, Object[]) line: 456   
    AbstractBeanFactory$1.getObject() line: 294 
    DefaultListableBeanFactory(DefaultSingletonBeanRegistry).getSingleton(String, ObjectFactory) line: 225  
    DefaultListableBeanFactory(AbstractBeanFactory).doGetBean(String, Class<T>, Object[], boolean) line: 291    
    DefaultListableBeanFactory(AbstractBeanFactory).getBean(String) line: 193   
    DefaultListableBeanFactory.preInstantiateSingletons() line: 585 
    GenericApplicationContext(AbstractApplicationContext).finishBeanFactoryInitialization(ConfigurableListableBeanFactory) line: 913    
    GenericApplicationContext(AbstractApplicationContext).refresh() line: 464   
    GenericXmlContextLoader(AbstractGenericContextLoader).loadContext(MergedContextConfiguration) line: 103 
    GenericXmlContextLoader(AbstractGenericContextLoader).loadContext(MergedContextConfiguration) line: 1   
    DelegatingSmartContextLoader.loadContext(MergedContextConfiguration) line: 228  
    TestContext.loadApplicationContext() line: 124  
    TestContext.getApplicationContext() line: 148   
    DependencyInjectionTestExecutionListener.injectDependencies(TestContext) line: 109  
    DependencyInjectionTestExecutionListener.prepareTestInstance(TestContext) line: 75  
    TestContextManager.prepareTestInstance(Object) line: 321    
    SpringJUnit4ClassRunner.createTest() line: 211  
    SpringJUnit4ClassRunner$1.runReflectiveCall() line: 288 
    SpringJUnit4ClassRunner$1(ReflectiveCallable).run() line: 15    
    SpringJUnit4ClassRunner.methodBlock(FrameworkMethod) line: 290  
    SpringJUnit4ClassRunner.runChild(FrameworkMethod, RunNotifier) line: 231    
    SpringJUnit4ClassRunner(BlockJUnit4ClassRunner).runChild(Object, RunNotifier) line: 44  
    SpringJUnit4ClassRunner(ParentRunner<T>).runChildren(RunNotifier) line: 180 
    ParentRunner<T>.access$000(ParentRunner, RunNotifier) line: 41  
    ParentRunner$1.evaluate() line: 173 
    RunBefores.evaluate() line: 28  
    RunBeforeTestClassCallbacks.evaluate() line: 61 
    RunAfters.evaluate() line: 31   
    RunAfterTestClassCallbacks.evaluate() line: 71  
    SpringJUnit4ClassRunner(ParentRunner<T>).run(RunNotifier) line: 220 
    SpringJUnit4ClassRunner.run(RunNotifier) line: 174  
    JUnit4TestMethodReference(JUnit4TestReference).run(TestExecution) line: 50  
    TestExecution.run(ITestReference[]) line: 38    
    RemoteTestRunner.runTests(String[], String, TestExecution) line: 467    
    RemoteTestRunner.runTests(TestExecution) line: 683  
    RemoteTestRunner.run() line: 390    
    RemoteTestRunner.main(String[]) line: 197

Second:

Thread [main] (Suspended (breakpoint at line 47 in SingletonClass)) 
    SingletonClass$$EnhancerByCGLIB$$e8a4cc48(SingletonClass).<init>() line: 47 
    SingletonClass$$EnhancerByCGLIB$$e8a4cc48.<init>() line: not available  
    NativeConstructorAccessorImpl.newInstance0(Constructor, Object[]) line: not available [native method]   
    NativeConstructorAccessorImpl.newInstance(Object[]) line: 57    
    DelegatingConstructorAccessorImpl.newInstance(Object[]) line: 45    
    Constructor<T>.newInstance(Object...) line: 525 
    ReflectUtils.newInstance(Constructor, Object[]) line: 228   
    ReflectUtils.newInstance(Class, Class[], Object[]) line: 220    
    ReflectUtils.newInstance(Class) line: 216   
    Enhancer.createUsingReflection(Class) line: 643 
    Enhancer.firstInstance(Class) line: 538 
    Enhancer(AbstractClassGenerator).create(Object) line: 225   
    Enhancer.createHelper() line: 377   
    Enhancer.create() line: 285 
    Cglib2AopProxy.getProxy(ClassLoader) line: 201  
    ProxyFactory.getProxy(ClassLoader) line: 112    
    InfrastructureAdvisorAutoProxyCreator(AbstractAutoProxyCreator).createProxy(Class<?>, String, Object[], TargetSource) line: 476 
    InfrastructureAdvisorAutoProxyCreator(AbstractAutoProxyCreator).wrapIfNecessary(Object, String, Object) line: 362   
    InfrastructureAdvisorAutoProxyCreator(AbstractAutoProxyCreator).postProcessAfterInitialization(Object, String) line: 322    
    DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).applyBeanPostProcessorsAfterInitialization(Object, String) line: 407 
    DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).initializeBean(String, Object, RootBeanDefinition) line: 1461    
    DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).doCreateBean(String, RootBeanDefinition, Object[]) line: 519 
    DefaultListableBeanFactory(AbstractAutowireCapableBeanFactory).createBean(String, RootBeanDefinition, Object[]) line: 456   
    AbstractBeanFactory$1.getObject() line: 294 
    DefaultListableBeanFactory(DefaultSingletonBeanRegistry).getSingleton(String, ObjectFactory) line: 225  
    DefaultListableBeanFactory(AbstractBeanFactory).doGetBean(String, Class<T>, Object[], boolean) line: 291    
    DefaultListableBeanFactory(AbstractBeanFactory).getBean(String) line: 193   
    DefaultListableBeanFactory.preInstantiateSingletons() line: 585 
    GenericApplicationContext(AbstractApplicationContext).finishBeanFactoryInitialization(ConfigurableListableBeanFactory) line: 913    
    GenericApplicationContext(AbstractApplicationContext).refresh() line: 464   
    GenericXmlContextLoader(AbstractGenericContextLoader).loadContext(MergedContextConfiguration) line: 103 
    GenericXmlContextLoader(AbstractGenericContextLoader).loadContext(MergedContextConfiguration) line: 1   
    DelegatingSmartContextLoader.loadContext(MergedContextConfiguration) line: 228  
    TestContext.loadApplicationContext() line: 124  
    TestContext.getApplicationContext() line: 148   
    DependencyInjectionTestExecutionListener.injectDependencies(TestContext) line: 109  
    DependencyInjectionTestExecutionListener.prepareTestInstance(TestContext) line: 75  
    TestContextManager.prepareTestInstance(Object) line: 321    
    SpringJUnit4ClassRunner.createTest() line: 211  
    SpringJUnit4ClassRunner$1.runReflectiveCall() line: 288 
    SpringJUnit4ClassRunner$1(ReflectiveCallable).run() line: 15    
    SpringJUnit4ClassRunner.methodBlock(FrameworkMethod) line: 290  
    SpringJUnit4ClassRunner.runChild(FrameworkMethod, RunNotifier) line: 231    
    SpringJUnit4ClassRunner(BlockJUnit4ClassRunner).runChild(Object, RunNotifier) line: 44  
    SpringJUnit4ClassRunner(ParentRunner<T>).runChildren(RunNotifier) line: 180 
    ParentRunner<T>.access$000(ParentRunner, RunNotifier) line: 41  
    ParentRunner$1.evaluate() line: 173 
    RunBefores.evaluate() line: 28  
    RunBeforeTestClassCallbacks.evaluate() line: 61 
    RunAfters.evaluate() line: 31   
    RunAfterTestClassCallbacks.evaluate() line: 71  
    SpringJUnit4ClassRunner(ParentRunner<T>).run(RunNotifier) line: 220 
    SpringJUnit4ClassRunner.run(RunNotifier) line: 174  
    JUnit4TestMethodReference(JUnit4TestReference).run(TestExecution) line: 50  
    TestExecution.run(ITestReference[]) line: 38    
    RemoteTestRunner.runTests(String[], String, TestExecution) line: 467    
    RemoteTestRunner.runTests(TestExecution) line: 683  
    RemoteTestRunner.run() line: 390    
    RemoteTestRunner.main(String[]) line: 197   

What is the reason for the constructor being called twice, and how can I prevent this from happening? While these singleton checks do seem to be throwing a wrench into the unit test environment, they have served well in the past as a good "hard" limit when actually in production.

Upvotes: 2

Views: 2113

Answers (1)

eis
eis

Reputation: 53563

I would claim the reason being that your singleton class doesn't expose an interface that Spring can use for its application context. Spring bases its autowiring on implementing an interface that acts as a singleton proxy for the class. Since however your singletonclass is an actual class, Spring cannot do that, but instead is forced to subclass your class using cglib (which you see in the stack trace). Any child class has to invoke the super constructor as well, so you see two invocations.

So, in short: I think appcontext is not the issue here. If your class would expose & implement an actual interface that Spring can implement with its proxies, like is recommended, I would suspect you wouldn't get your exception.

Upvotes: 2

Related Questions