You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JUnit5+Spring+Docker集成测试:@Sql执行时机问题求助

我来帮你搞定这个问题!你遇到的核心痛点就是Spring的@Sql注解跑脚本的时机太早,PostgreSQL Docker容器还没完全就绪呢,脚本就已经执行了,导致测试失败对吧?下面给你几个靠谱的解决方案,从最省心的官方集成到灵活自定义的方式都有:

方案1:改用Testcontainers官方集成(首推)

放弃你现在用的第三方@Docker注解,换成Testcontainers的官方JUnit5支持,它和Spring的集成非常成熟,能自动帮你协调容器启动、就绪等待和Spring上下文初始化的顺序,根本不用操心脚本执行时机的问题。

具体步骤:

  1. 先添加Testcontainers的相关依赖(如果还没加的话):
    (比如Maven项目添加org.testcontainers:junit-jupiter和org.testcontainers:postgresql依赖)
  2. 修改你的测试类,用Testcontainers的注解替代原来的@Docker:
@Testcontainers
@ExtendWith(SpringExtension::class)
@Sql(scripts = ["/data/clear.sql", "/data/operations.sql"])
@ContextConfiguration(classes = [DockerContainerConfig::class])
class OperationSearchRepositoryImplTest {

    companion object {
        // 定义PostgreSQL容器,配置参数和等待条件
        @Container
        val postgresContainer = PostgreSQLContainer("postgres:9.6")
            .withDatabaseName("nv2test")
            .withUsername("postgres")
            .withPassword("postgres")
            .waitingFor(Wait.forLogMessage(".*database system is ready to accept connections.*", 1))

        // 动态向Spring注册数据源配置,让Spring自动使用容器的连接信息
        @JvmStatic
        @DynamicPropertySource
        fun registerDataSourceProperties(registry: DynamicPropertyRegistry) {
            registry.add("spring.datasource.url", postgresContainer::getJdbcUrl)
            registry.add("spring.datasource.username", postgresContainer::getUsername)
            registry.add("spring.datasource.password", postgresContainer::getPassword)
        }
    }

    @Test
    fun shouldFindByExample() {
        // 你的测试逻辑
    }
}
  1. 调整你的DockerContainerConfig:要么让它读取动态注册的数据源属性,要么直接用Spring Boot的自动配置(如果是Spring Boot项目的话),这样数据源会自动关联到就绪的容器。

这个方案的好处是完全不用手动处理顺序问题,Testcontainers会确保容器完全就绪后,才让Spring初始化数据源,之后@Sql脚本自然就会在正确的时机执行。

方案2:自定义TestExecutionListener控制脚本执行时机

如果你不想换用Testcontainers,想继续用现有的@Docker注解,那可以自定义一个TestExecutionListener,先等容器就绪,再触发@Sql脚本的执行。

具体步骤:

  1. 写一个自定义的Listener:
class DockerReadySqlListener : AbstractTestExecutionListener() {

    override fun getOrder(): Int {
        // 让这个Listener比SqlScriptsTestExecutionListener先执行
        return SqlScriptsTestExecutionListener.DEFAULT_ORDER - 1
    }

    override fun beforeTestClass(testContext: TestContext) {
        // 获取你用的Docker扩展实例,等待容器就绪
        // 这里的DockerExtension要替换成你实际用的那个扩展类
        val dockerExtension = testContext.testExecutionListeners
            .filterIsInstance<DockerExtension>()
            .firstOrNull()
        
        dockerExtension?.let {
            // 调用扩展的等待方法,确保容器完全就绪
            it.waitForContainers(testContext)
        }
    }
}
  1. 在测试类中注册这个自定义Listener:
@Docker(
    image = "postgres:9.6", 
    ports = [Port(exposed = 5433, inner = 5432)], 
    environments = [ 
        Environment(key = "POSTGRES_DB", value = "nv2test"), 
        Environment(key = "POSTGRES_USER", value = "postgres"), 
        Environment(key = "POSTGRES_PASSWORD", value = "postgres")], 
    waitFor = WaitFor("LOG: database system is ready to accept connections"), 
    newForEachCase = false
) 
@Sql(scripts = ["/data/clear.sql", "/data/operations.sql"]) 
@ExtendWith(SpringExtension::class) 
@TestExecutionListeners(
    value = [
        DockerReadySqlListener::class,
        SqlScriptsTestExecutionListener::class,
        DependencyInjectionTestExecutionListener::class
    ],
    mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS
)
@ContextConfiguration(classes = [DockerContainerConfig::class]) 
class OperationSearchRepositoryImplTest{ 
    @Test 
    fun shouldFindByExample() { 
        // 你的测试逻辑
    } 
}

这个方法通过调整Listener的执行顺序,强制先等待容器就绪,再执行SQL脚本,完美解决顺序问题。

方案3:手动执行SQL脚本(最灵活)

如果上面两种方案都不适合你,那可以干脆去掉@Sql注解,手动在测试类中控制脚本的执行时机,确保容器和数据源都就绪后再跑脚本。

具体步骤:

@Docker(...) // 保留你原来的@Docker注解
@ExtendWith(SpringExtension::class)
@ContextConfiguration(classes = [DockerContainerConfig::class])
class OperationSearchRepositoryImplTest {

    @Autowired
    lateinit var jdbcTemplate: JdbcTemplate

    @BeforeAll
    fun setupDatabase() {
        // 执行SQL脚本,这里用Spring的ScriptUtils来处理脚本文件
        executeSqlScript("/data/clear.sql")
        executeSqlScript("/data/operations.sql")
    }

    private fun executeSqlScript(scriptPath: String) {
        val scriptResource = ClassPathResource(scriptPath)
        ScriptUtils.executeSqlScript(jdbcTemplate.dataSource!!.connection, scriptResource)
    }

    @Test
    fun shouldFindByExample() {
        // 你的测试逻辑
    }
}

这个方式完全由你掌控脚本的执行时机,@BeforeAll方法会在所有测试方法之前执行,这时候容器和数据源肯定已经就绪了。


内容的提问来源于stack exchange,提问作者Sam Clarenc

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 03:54:45