Home > Blockchain >  how to specify nameGenerator in SpringBootTest?
how to specify nameGenerator in SpringBootTest?

Time:12-13

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!)

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)
    }
}
  • Related