Kotlin Spring Boot集成Test Containers报ParameterResolutionException求助
Kotlin Spring Boot集成TestContainers启动失败问题排查
异常信息
org.junit.jupiter.api.extension.ParameterResolutionException: Failed to resolve parameter [bootiful.kotlin.CustomerRepository customerRepository] in constructor [public bootiful.kotlin.KotlinApplicationTests(bootiful.kotlin.CustomerRepository)]: Failed to load ApplicationContext for [ReactiveWebMergedContextConfiguration@55fb36de testClass = bootiful.kotlin.KotlinApplicationTests, locations = [], classes = [bootiful.kotlin.KotlinApplication, bootiful.kotlin.CustomerRepository], contextInitializerClasses = [], activeProfiles = [], propertySourceLocations = [], propertySourceProperties = ["org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true"], contextCustomizers = [org.springframework.boot.testcontainers.service.connection.ServiceConnectionContextCustomizer@0, org.springframework.boot.test.autoconfigure.actuate.observability.ObservabilityContextCustomizerFactory$DisableObservabilityContextCustomizer@1f, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@a4ca3f6, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@b6b1987, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@7c541c15, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@3f0846c6, org.springframework.boot.test.web.reactive.server.WebTestClientContextCustomizer@3e10dc6, org.springframework.boot.test.context.SpringBootTestAnnotation@5de402fc], contextLoader = org.springframework.boot.test.context.SpringBootContextLoader, parent = null] at org.junit.jupiter.engine.execution.ParameterResolutionUtils.resolveParameter(ParameterResolutionUtils.java:159) ...(省略部分栈信息) Caused by: java.lang.IllegalStateException: Failed to load ApplicationContext for [ReactiveWebMergedContextConfiguration@55fb36de testClass = bootiful.kotlin.KotlinApplicationTests, locations = [], classes = [bootiful.kotlin.KotlinApplication, bootiful.kotlin.CustomerRepository], contextInitializerClasses = [], activeProfiles = [], propertySourceLocations = [], propertySourceProperties = ["org.springframework.boot.test.context.SpringBootTestContextBootstrapper=true"], contextCustomizers = [org.springframework.boot.testcontainers.service.connection.ServiceConnectionContextCustomizer@0, org.springframework.boot.test.autoconfigure.actuate.observability.ObservabilityContextCustomizerFactory$DisableObservabilityContextCustomizer@1f, org.springframework.boot.test.autoconfigure.properties.PropertyMappingContextCustomizer@0, org.springframework.boot.test.autoconfigure.web.servlet.WebDriverContextCustomizerFactory$Customizer@a4ca3f6, org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizer@b6b1987, org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory$DuplicateJsonObjectContextCustomizer@7c541c15, org.springframework.boot.test.mock.mockito.MockitoContextCustomizer@0, org.springframework.boot.test.web.client.TestRestTemplateContextCustomizer@3f0846c6, org.springframework.boot.test.web.reactive.server.WebTestClientContextCustomizer@3e10dc6, org.springframework.boot.test.context.SpringBootTestAnnotation@5de402fc], contextLoader = org.springframework.boot.test.context.SpringBootContextLoader, parent = null]
相关配置与代码
主目录application.properties
spring.r2dbc.url=r2dbc:postgresql://localhost/postgres spring.r2dbc.username=postgres spring.r2dbc.password=postgres spring.sql.init.mode=always
测试目录application.properties
spring.sql.init.mode=always spring.r2dbc.url=r2dbc:tc:postgresql:///customers?TC_IMAGE_TAG=13.3
测试类代码
@SpringBootTest class KotlinApplicationTests(@Autowired val customerRepository: CustomerRepository) { @Test fun contextLoads() { runBlocking { customerRepository.save(Customer(null, "Hadi")) val customers = customerRepository.findAll() Assertions.assertNotNull(customers.last().id) Assertions.assertEquals(customers.count(), 6) } } }
TestContainers相关代码
class TestKotlinApplication { @Bean @ServiceConnection fun postgresContainer(): PostgreSQLContainer<*> { return PostgreSQLContainer("postgres:latest") } } fun main(args: Array<String>) { fromApplication<KotlinApplication>().with(TestKotlinApplication::class).run(*args) }
主应用类代码
@SpringBootApplication class KotlinApplication fun main(args: Array<String>) { runApplication<KotlinApplication>(*args) } interface CustomerRepository:CoroutineCrudRepository<Customer,Int>{} data class Customer(@Id val id: Int?, val name:String){}
build.gradle.kts配置
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { id("org.springframework.boot") version "3.1.2" id("io.spring.dependency-management") version "1.1.2" id("org.graalvm.buildtools.native") version "0.9.23" kotlin("jvm") version "1.8.20" kotlin("plugin.spring") version "1.8.20" } group = "bootiful" version = "0.0.1-SNAPSHOT" java { sourceCompatibility = JavaVersion.VERSION_17 } repositories { mavenCentral() } dependencies { implementation("org.springframework.boot:spring-boot-starter-data-r2dbc") implementation("org.springframework.boot:spring-boot-starter-webflux") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("io.projectreactor.kotlin:reactor-kotlin-extensions") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor") runtimeOnly("org.postgresql:postgresql") runtimeOnly("org.postgresql:r2dbc-postgresql") testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.springframework.boot:spring-boot-testcontainers") testImplementation("io.projectreactor:reactor-test") testImplementation("org.testcontainers:junit-jupiter") testImplementation("org.testcontainers:postgresql") testImplementation("org.testcontainers:r2dbc") } tasks.withType<KotlinCompile> { kotlinOptions { freeCompilerArgs += "-Xjsr305=strict" jvmTarget = "17" } } tasks.withType<Test> { useJUnitPlatform() }
错误原因分析
- TestContainers配置冲突:同时使用了两种TestContainers集成方式——
r2dbc:tc:URL协议自动启动容器,以及手动定义@ServiceConnection的PostgreSQLContainerBean,两种方式会导致Spring无法正确解析数据库连接配置,进而无法初始化ApplicationContext。 - 测试上下文未加载Test配置类:
TestKotlinApplication类中的容器Bean未被测试上下文扫描到,即使没有冲突,也无法正常启动容器,但核心问题还是配置冲突。
解决方案
方案一:使用r2dbc:tc: URL协议(推荐,更简洁)
- 删除
TestKotlinApplication类及其主方法,无需手动定义容器Bean,r2dbc:tc:协议会自动启动对应版本的PostgreSQL容器。 - 保留测试目录
application.properties中的配置:spring.sql.init.mode=always spring.r2dbc.url=r2dbc:tc:postgresql:///customers?TC_IMAGE_TAG=13.3 - 测试类保持不变,构造器注入
CustomerRepository是合法的(Spring Boot Test支持构造器自动注入,无需@Autowired也可,保留也没问题)。
方案二:使用@ServiceConnection Bean方式
- 删除测试目录
application.properties中的spring.r2dbc.url配置,@ServiceConnection会自动将容器的连接信息绑定到Spring的R2DBC配置中。 - 修改测试类,确保
TestKotlinApplication被测试上下文加载:@SpringBootTest(classes = [KotlinApplication::class, TestKotlinApplication::class]) class KotlinApplicationTests(val customerRepository: CustomerRepository) { // 测试方法不变 } - 建议将
PostgreSQLContainer的镜像版本改为postgres:13.3,与之前URL中的版本一致,避免版本差异问题:@Bean @ServiceConnection fun postgresContainer(): PostgreSQLContainer<*> { return PostgreSQLContainer("postgres:13.3") }
验证
修改后运行测试,Spring会正确启动TestContainers PostgreSQL容器,初始化数据库连接,ApplicationContext正常加载,测试即可执行。
内容的提问来源于stack exchange,提问作者Edgar
相关产品推荐
相关产品推荐

