我正在尝试使用 Spring Boot 在 Kotlin 中编写一个简单的 @WebMvcTest。 Test 下的控制器依赖于一个使用 @ConfigurationProperties 注释的 Configuration 类,以从我的 application-*.yml 文件中提取一个简单的字符串属性。我有一个 application-test.yml 文件,它定义了此属性,并使用 @ActiveProfile("test") 注释测试类以使用此属性。因为 @WebMvcTest 是一个切片,默认情况下不会加载我的配置,所以我还添加了 @Import(ApplicationConfiguration::class)。
测试代码现在在尝试构造 ApplicationConfiguration Bean 时失败,因为它找不到该属性。常规应用程序运行良好,并通过 @ConfigurationPropertiesScan 查找属性。
我在发布之前整理了我的代码并更改了一些名称,但以下代码仍然以同样的方式失败。
这是堆栈跟踪的底部:
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'com.example.vpsxprintproxy.configuration.ApplicationConfiguration': Unsatisfied dependency expressed through constructor parameter 0: No qualifying bean of type 'java.lang.String' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:800)
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:245)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1352)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1189)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:560)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:520)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1417)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1337)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:888)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
... 96 common frames omitted
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'java.lang.String' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1824)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1383)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1337)
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:888)
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)
... 110 common frames omitted
相关课程:
@WebMvcTest(StatusController::class)
@Import(ApplicationConfiguration::class)
@ActiveProfiles("test")
class StatusControllerTest {
@Autowired
private lateinit var mockMvc: MockMvc
@Test
fun getStatus() {
mockMvc.perform(MockMvcRequestBuilders.get("/status"))
.andExpect(MockMvcResultMatchers.status().isOk)
.andExpect(
MockMvcResultMatchers.content().string("Application version someApplicationVersion is up and running.")
)
}
}
@RestController
@RequestMapping("/status")
class StatusController(
private val applicationConfiguration: ApplicationConfiguration
) {
@GetMapping
fun getStatus(): String = "Application version ${applicationConfiguration.version} is up and running."
}
@ConfigurationProperties(prefix = "application")
data class ApplicationConfiguration(
val version: String
)
还有 application-test.yml 文件:
application:
version: someApplicationVersion
我尝试过的另一件事导致了奇怪的行为:我已经将我的 ApplicationConfiguration 更改为具有像这样的默认值
@ConfigurationProperties(prefix = "application")
data class ApplicationConfiguration(
var version: String = "default"
)
当我这样做时,测试不仅成功地找到了版本的some值,而且还成功地找到了版本的值。它从 application-test.yml 中找到正确值。
我知道我可以定义自己的测试配置类和/或模拟它来运行这个特定的测试,但这在具有许多属性的类中会很麻烦,我很好奇为什么这种行为存在以及我如何使用我的 application-test.yml 与 @WebMvcTest。我也不想将其设为@SpringBootTest,因为应用程序可能会变得很大,而且我不想启动整个上下文。
有人可以解释这种行为和/或如何使我的 ApplicationConfiguration 实例化 application-test.yml 中的属性吗?
这实际上是切片测试中发生的一个常见问题,并且随着主要 Spring Boot 版本之间的行为变化而加剧。
我认为在某些时候切片测试(例如@WebMvcTest、@DataJpaTest...)确实将@ConfigurationProperties bean导入到切片上下文中,但我找不到任何东西来支持我的主张。
Spring 文档指出:
使用@WebMvcTest注释时,不会扫描常规@Component和@ConfigurationProperties bean。 @EnableConfigurationProperties 可用于包含 @ConfigurationProperties beans。
在你的情况下,你应该以明确的方式导入你的属性bean:
@EnableConfigurationProperties([ApplicationConfiguration::class])