Testcontainers集成MSSQL测试时@Autowired注入Repository失败
问题描述
使用Testcontainers基于MSSQL Server镜像开展Spring Data JPA集成测试时,注入Repository抛出如下错误:
lateinit property modelRepository has not been initialized
相同测试配置在PostgreSQL项目中可正常运行。
相关代码
测试类
package *.models.integration import *.portadapter.repository.ModelRepository import *.builders.ModelBuilder import org.junit.ClassRule import org.junit.Test import org.junit.jupiter.api.Assertions.assertEquals import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import org.springframework.test.context.DynamicPropertyRegistry import org.springframework.test.context.DynamicPropertySource import org.testcontainers.containers.MSSQLServerContainer import org.testcontainers.containers.startupcheck.MinimumDurationRunningStartupCheckStrategy import org.testcontainers.junit.jupiter.Testcontainers import java.time.Duration import java.util.function.Supplier @DataJpaTest @Testcontainers @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) class ModelRepositoryTest { @Autowired private lateinit var modelRepository: ModelRepository companion object { private val hibernateDriver: Supplier<Any> = Supplier { "org.hibernate.dialect.SQLServerDialect" } private val hibernateDll: Supplier<Any> = Supplier { "none" } @ClassRule @JvmField val mssqlserver: MSSQLServerContainer<*> = MSSQLServerContainer("mcr.microsoft.com/mssql/server:2017-latest") .withStartupCheckStrategy(MinimumDurationRunningStartupCheckStrategy(Duration.ofSeconds(5))) .acceptLicense() @JvmStatic @DynamicPropertySource fun properties(registry: DynamicPropertyRegistry) { registry.add("spring.datasource.url", mssqlserver::getJdbcUrl) registry.add("spring.datasource.username", mssqlserver::getUsername) registry.add("spring.datasource.password", mssqlserver::getPassword) registry.add("spring.jpa.properties.hibernate.dialect", hibernateDriver) registry.add("spring.jpa.hibernate.ddl-auto", hibernateDll) mssqlserver.acceptLicense().start() } } @Test fun `it should save a model`() { val model = ModelBuilder().createModel().get() modelRepository.save(model) val modelFound = modelRepository.findByCode(model.code) assertEquals(model.code, modelFound.get().code) } }
Repository类
package *.portadapter.repository import *.model.domain.Model import org.springframework.data.jpa.repository.JpaRepository import java.util.* interface ModelRepository: JpaRepository<Model, String> { fun findByCode(code: String): Optional<Model> }
问题根因
一共三个核心问题:
- 包扫描路径不匹配:
@DataJpaTest默认仅扫描测试类所在包及其子包下的Spring Data Repository、实体类。当前测试类在*.models.integration包,而ModelRepository在*.portadapter.repository包,不在默认扫描范围内,Spring上下文未生成Repository对应的Bean,是注入失败的直接原因。PostgreSQL场景可正常运行,大概率是对应项目中测试类与Repository包路径符合默认扫描规则,或手动配置过扫描路径。 - JUnit版本注解混用:代码中同时混用JUnit4和JUnit5(Jupiter)的注解:
@ClassRule、org.junit.Test属于JUnit4组件,@Testcontainers、@DynamicPropertySource属于JUnit5组件。混用会导致容器生命周期管理、属性注入逻辑异常,引发上下文启动不稳定。 - 冗余配置错误:
@DynamicPropertySource标注的方法中手动调用mssqlserver.acceptLicense().start()属于重复逻辑,Testcontainers会自动管理容器生命周期,手动启动可能引发端口冲突、重复启动等问题;另外spring.jpa.hibernate.ddl-auto设为none会导致测试时不会自动创建表结构,后续执行数据库操作会报表不存在错误。
修复方案
按以下步骤调整代码即可:
- 配置
@DataJpaTest的扫描路径,确保Spring能找到Repository和实体类,替换类上的@DataJpaTest注解为:
注意将包路径替换为项目实际的Repository、实体类根包路径。@DataJpaTest(basePackages = ["*.portadapter.repository"]) @EntityScan(basePackages = ["*.model.domain"]) - 统一使用JUnit5注解,移除JUnit4相关配置:
- 删除
import org.junit.Test,替换为import org.junit.jupiter.api.Test - 删除容器字段上的
@ClassRule、@JvmField注解,替换为Testcontainers for JUnit5的@Container注解
- 删除
- 移除冗余配置,修正Hibernate DDL配置:
- 删除properties方法中
mssqlserver.acceptLicense().start()这行冗余调用 - 将
spring.jpa.hibernate.ddl-auto配置改为create-drop,测试时自动建表、测试结束自动删表,不残留测试数据
- 删除properties方法中
修复后完整测试类参考
package *.models.integration import *.portadapter.repository.ModelRepository import *.builders.ModelBuilder import org.junit.jupiter.api.Test import org.junit.jupiter.api.Assertions.assertEquals import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.autoconfigure.domain.EntityScan import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest import org.springframework.test.context.DynamicPropertyRegistry import org.springframework.test.context.DynamicPropertySource import org.testcontainers.containers.MSSQLServerContainer import org.testcontainers.containers.startupcheck.MinimumDurationRunningStartupCheckStrategy import org.testcontainers.junit.jupiter.Container import org.testcontainers.junit.jupiter.Testcontainers import java.time.Duration @DataJpaTest(basePackages = ["*.portadapter.repository"]) @EntityScan(basePackages = ["*.model.domain"]) @Testcontainers @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) class ModelRepositoryTest { @Autowired private lateinit var modelRepository: ModelRepository companion object { @Container @JvmField val mssqlserver: MSSQLServerContainer<*> = MSSQLServerContainer("mcr.microsoft.com/mssql/server:2017-latest") .withStartupCheckStrategy(MinimumDurationRunningStartupCheckStrategy(Duration.ofSeconds(5))) .acceptLicense() @JvmStatic @DynamicPropertySource fun properties(registry: DynamicPropertyRegistry) { registry.add("spring.datasource.url", mssqlserver::getJdbcUrl) registry.add("spring.datasource.username", mssqlserver::getUsername) registry.add("spring.datasource.password", mssqlserver::getPassword) registry.add("spring.jpa.properties.hibernate.dialect") { "org.hibernate.dialect.SQLServerDialect" } registry.add("spring.jpa.hibernate.ddl-auto") { "create-drop" } } } @Test fun `it should save a model`() { val model = ModelBuilder().createModel().get() modelRepository.save(model) val modelFound = modelRepository.findByCode(model.code) assertEquals(model.code, modelFound.get().code) } }
内容的提问来源于stack exchange,提问作者Carlos Eduardo Ilgenfritz
相关产品推荐
相关产品推荐

