I am trying to run @SpringBootTest
with a subset of classes. There are 2 beans with conflicting names among these classes.
@SpringBootTest(
classes = [BarService::class, ConflictName::class, com.foo.ConflictName::class, FooService::class]
)
class DemoApplicationTests
The test fails with BeanDefinitionOverrideException
Caused by: org.springframework.beans.factory.support.BeanDefinitionOverrideException:
Invalid bean definition with name 'conflictName' defined in null:
Cannot register bean definition [Generic bean: class [com.foo.ConflictName]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=null]
for bean 'conflictName' since there is already [Generic bean: class [com.bar.ConflictName]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=null] bound.
However, if the test is running for the whole application, without specifying the concrete classes
@SpringBootTest
class DemoApplicationTests
the execution is successful.
The question is how to specify nameGenerator
in SpringBootTest
similar to @SpringBootApplication(nameGenerator = FullyQualifiedAnnotationBeanNameGenerator::class)
?
the complete code example:
package com.bar
import org.springframework.stereotype.Service
@Service
class BarService(private val conflictName: ConflictName)
========================================================
package com.bar
import org.springframework.stereotype.Component
@Component
class ConflictName
========================================================
package com.foo
import org.springframework.stereotype.Component
@Component
class ConflictName
========================================================
package com.foo
import org.springframework.stereotype.Service
@Service
class FooService(private val conflictName: ConflictName)
========================================================
package com
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.FullyQualifiedAnnotationBeanNameGenerator
@SpringBootApplication(nameGenerator = FullyQualifiedAnnotationBeanNameGenerator::class)
class DemoApplication
fun main(args: Array<String>) {
runApplication<DemoApplication>(*args)
}
CodePudding user response:
To achieve the same as @SpringBootApplication(nameGenerator = FullyQualifiedAnnotationBeanNameGenerator::class)
in a (isolated configuration) test, we can:
- declare an "internal"
@Configuration
class (which will skip/replace spring boot application loading... to "add/modify" (loaded) spring boot application, use@TestConfiguration
(internal class)). - use
@ComponentScan
on that, which:- takes a
nameGenerator:Class<...>
parameter (, which exactly is "aliased" by@SpringBootApplication
). - if omit, it falls back to "current package", but if we use
basePackageClasses
, we can cherry pick our components. (But please note it is (as in@SpringBootApplication
) package- not class-wise!)
- takes a
A sample test:
package com
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.FullyQualifiedAnnotationBeanNameGenerator
@SpringBootTest
class DemoApplicationTests {
@Configuration // only this will be used for this test class!
@ComponentScan(
nameGenerator = FullyQualifiedAnnotationBeanNameGenerator::class,
basePackageClasses = [com.foo.ConflictName::class, com.bar.ConflictName::class]
)
// empty/customize:
internal class IsolatedTestConfig
@Autowired(required = false)
var springBootApp: org.springframework.boot.SpringApplication? = null
@Autowired(required = false)
var compFoo: com.foo.ConflictName? = null
@Autowired(required = false)
var compBar: com.bar.ConflictName? = null
@Test
fun testNamingAndIsolation() {
Assertions.assertNull(springBootApp) // !
Assertions.assertNotNull(compFoo)
Assertions.assertNotNull(compBar)
}
}