Skip to content

JUnit Jupiter @Nested class cannot share enclosing class's ApplicationContext if nested class is deemed to be a configuration candidate [SPR-16595] #21136

Closed
@spring-projects-issues

Description

@spring-projects-issues

Andy Wilkinson opened SPR-16595 and commented

This feels like a bug, although it may just be a (rather large) limitation.

It appears to be impossible for an inner test class annotated with @Nested to use the enclosing class's application context if the enclosing class is deemed to be a configuration candidate (for example, because it uses @Import).

To be able to share the context, the inner test class must have the same configuration as its enclosing class so that they have the same context cache key. In other words, if the enclosing class has used @Import the inner class must do so too. This leads to code like this:

import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit.jupiter.SpringExtension;

@ExtendWith(SpringExtension.class)
@Import(Imported.class)
class NestedLimitationsTests {

	@Nested
	@Import(Imported.class)
	class NestedTests {

		@Test
		public void test() {

		}

	}

}

class Imported {
}

This fails to launch with the following exception:

Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'example.NestedLimitationsTests$NestedTests': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'example.NestedLimitationsTests' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:729)
	at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:192)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1270)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1127)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:545)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:502)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:312)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:310)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:760)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:868)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549)
	at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:128)
	at org.springframework.test.context.support.AbstractGenericContextLoader.loadContext(AbstractGenericContextLoader.java:60)
	at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.delegateLoading(AbstractDelegatingSmartContextLoader.java:107)
	at org.springframework.test.context.support.AbstractDelegatingSmartContextLoader.loadContext(AbstractDelegatingSmartContextLoader.java:251)
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99)
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:117)
	... 88 common frames omitted
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'example.NestedLimitationsTests' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1509)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1104)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1065)
	at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:815)
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:721)
	... 106 common frames omitted

An attempt is being made to create a NestedTests bean as its @Import annotation means that ConfigurationClassUtils considers it to be a configuration candidate. An instance of the enclosing class is needed to create NestedTests as the class is not static. It fails because the enclosing class isn't available as a bean.

Declaring the inner class as static overcomes the immediate problem but creates another. Being static prevents the inner class from accessing the enclosing class's non-static fields and declaring the enclosing class's fields as static means that they're no longer autowired.

The situation that's described above means that many of Spring Boot's testing annotations do not work. In fact, as far as I can tell, the only one that does work is @SpringBootTest as it is not meta-annotated with @Import.


Affects: 5.0.4

Reference URL: spring-projects/spring-restdocs#490

Issue Links:

Referenced from: commits spring-attic/spring-framework-issues@7a3db4a

1 votes, 6 watchers

Metadata

Metadata

Assignees

Labels

in: testIssues in the test moduletype: bugA general bug

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions