Kotlin测试中如何创建ApplicationContext并解决lateinit未初始化报错
问题根因
- 成员变量初始化时机错误:
beanFactory是类级别的成员变量,会在TemplateFactory实例构造阶段完成初始化,而@Autowired注入是在类实例创建完成后才执行的,此时applicationContext还未完成赋值,访问未初始化的lateinit变量直接触发异常。 - 注解使用位置错误:
@ContextConfiguration是Spring Test模块专属注解,仅对测试类生效,用于指定测试上下文的配置来源,标注在普通@Configuration类上不会被Spring识别,对应的xml配置不会被加载,配置类的依赖注入也不会执行。 - 测试类缺少上下文启动注解:如果你的测试类没有添加
@SpringBootTest、@ExtendWith(SpringExtension::class)(JUnit5)或@RunWith(SpringRunner::class)(JUnit4)注解,Spring测试上下文根本不会启动,自然无法完成ApplicationContext的注入。
修复方案
- 调整
beanFactory的获取时机,改为懒加载,避免类初始化阶段就访问未注入的applicationContext - 将
@ContextConfiguration移到测试类上,同时给测试类加上Spring测试启动注解 - 可选:实现
ApplicationContextAware接口主动接收上下文注入,比@Autowired更稳定
修复后的TemplateFactory代码:
package com.wayfair.samworkgroupsservice.adapter import io.r2dbc.mssql.MssqlConnectionConfiguration import io.r2dbc.mssql.MssqlConnectionFactory import org.springframework.beans.BeansException import org.springframework.beans.factory.config.ConstructorArgumentValues import org.springframework.beans.factory.support.BeanDefinitionRegistry import org.springframework.beans.factory.support.GenericBeanDefinition import org.springframework.context.ApplicationContext import org.springframework.context.ApplicationContextAware import org.springframework.context.annotation.Configuration import org.springframework.data.r2dbc.core.DefaultReactiveDataAccessStrategy import org.springframework.data.r2dbc.core.R2dbcEntityTemplate import org.springframework.data.r2dbc.dialect.SqlServerDialect import org.springframework.r2dbc.core.DatabaseClient @Configuration class TemplateFactory : ApplicationContextAware { private lateinit var applicationContext: ApplicationContext // 改为懒加载,第一次使用时才会初始化,此时applicationContext已经注入完成 private val beanFactory: BeanDefinitionRegistry by lazy { applicationContext.autowireCapableBeanFactory as BeanDefinitionRegistry } override fun setApplicationContext(applicationContext: ApplicationContext) { this.applicationContext = applicationContext } fun registerTemplateBean(host: String, port: Int) { val beanDefinition = GenericBeanDefinition() beanDefinition.beanClass = R2dbcEntityTemplate::class.java val args = ConstructorArgumentValues() args.addIndexedArgumentValue( 0, DatabaseClient.builder() .connectionFactory(connectionFactory(host, port)) .bindMarkers(SqlServerDialect.INSTANCE.bindMarkersFactory) .build() ) args.addIndexedArgumentValue(1, DefaultReactiveDataAccessStrategy(SqlServerDialect.INSTANCE)) beanDefinition.constructorArgumentValues = args beanFactory.registerBeanDefinition("R2dbcEntityTemplate", beanDefinition) } private fun connectionFactory(host: String, port: Int) = MssqlConnectionFactory( MssqlConnectionConfiguration.builder() .host(host) .port(port) .username("sa") .password("Password123@#?") .build() ) }
对应的测试类写法示例(JUnit5):
import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.springframework.beans.factory.annotation.Autowired import org.springframework.test.context.ContextConfiguration import org.springframework.test.context.junit.jupiter.SpringExtension @ExtendWith(SpringExtension::class) @ContextConfiguration("classpath:/testContext.xml", classes = [TemplateFactory::class]) class TemplateFactoryTest { @Autowired private lateinit var templateFactory: TemplateFactory @Test fun testRegisterBean() { templateFactory.registerTemplateBean("localhost", 1435) // 后续测试逻辑 } }
内容的提问来源于stack exchange,提问作者Lucas
相关产品推荐
相关产品推荐

