Mat*_*t R 7 java spring spring-mvc spring-boot testcontainers
我在 Spring boot 应用程序中有一套集成测试。测试上下文使用 MSSQL docker 容器作为其数据库,并使用testcontainers框架。
我的一些测试使用 Mockito 和 SpyBean,这显然是有意设计的,它将重新启动 Spring 上下文,因为监视的 bean 不能在测试之间共享。
由于我使用的是在所有测试期间都存在的非嵌入式数据库,因此通过在开始时执行我的 schema.sql 和 data.sql 来配置数据库:-
spring.datasource.initialization-mode=always
Run Code Online (Sandbox Code Playgroud)
问题是,当 Spring 上下文重新启动时,我的数据库再次重新初始化,这会触发诸如唯一约束问题、表已存在等错误。
我的父测试类如下(如果有帮助的话):-
@ActiveProfiles(Profiles.PROFILE_TEST)
@Testcontainers
@SpringJUnitWebConfig
@AutoConfigureMockMvc
@SpringBootTest(classes = Application.class)
@ContextConfiguration(initializers = {IntegrationTest.Initializer.class})
public abstract class IntegrationTest {
private static final MSSQLServerContainer<?> mssqlContainer;
static {
mssqlContainer = new MSSQLServerContainer<>()
.withInitScript("setup.sql"); //Creates users/permissions etc
mssqlContainer.start();
}
static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(final ConfigurableApplicationContext configurableApplicationContext) {
TestPropertyValues.of("spring.datasource.url=" + mssqlContainer.getJdbcUrl())
.applyTo(configurableApplicationContext.getEnvironment());
}
}
}
Run Code Online (Sandbox Code Playgroud)
每个集成测试都对此进行了扩展,以便共享上下文(对于非间谍测试)并且设置仅发生一次。
我希望能够在启动时仅执行一次启动脚本,并且无论上下文重新加载多少次,都不再执行一次。如果 Spring 测试框架能够记住我已经配置了一个数据库,那就太理想了。
我想知道是否有任何现有的配置或挂钩可以帮助我
如果有像下面这样的东西那就完美了。
spring.datasource.initialization-mode=always-once
Run Code Online (Sandbox Code Playgroud)
但是,据我所知,事实并非如此:(
new MSSQLServerContainer<>().withInitScript("setup.sql");
Run Code Online (Sandbox Code Playgroud)
这有效并确保我只能第一次运行启动脚本,因为容器仅启动一次。但是withInitScript仅接受单个参数而不是数组。因此,我需要将所有脚本连接到一个文件中,这意味着我必须维护两组脚本。
如果您只有一个脚本,那么这会很好地工作。
spring.datasource.continue-on-error=true
Run Code Online (Sandbox Code Playgroud)
从某种意义上说,这是有效的,模式中的启动错误将被忽略。但是..如果有人在脚本中添加了一些不可靠的 SQL,我希望它在启动时失败。
我无法让它发挥作用。我的想法是我可以监听 ContextRefreshedEvent 然后为spring.datasource.initialization-mode=never注入一个新值。
这有点黑客,但我尝试了类似以下的方法
@Component
public static class EventListener implements ApplicationListener<ApplicationEvent> {
@Autowired
private ConfigurableEnvironment environment;
@Override
public void onApplicationEvent(final ApplicationEvent event) {
log.info(event.getClass().getSimpleName());
if (event instanceof ContextRefreshedEvent) {
TestPropertyValues.of("spring.datasource.initialization-mode=never")
.applyTo(this.environment);
}
}
}
Run Code Online (Sandbox Code Playgroud)
我的猜测是,当上下文重新启动时,它还会再次重新加载所有具有mode=always的原始属性源。我需要在属性加载之后和模式创建发生之前发生一个事件。
那么,有人有什么建议吗?
所以我最终找到了解决这个问题的方法。感觉很老套,但除非其他人能够提出更合适且不那么晦涩的修复方案,否则这就是我会采用的方法。
该解决方案结合使用了 AtomicBoolean 的 @tsarenkotxt 建议和我的 #3 部分解决方案。
@ActiveProfiles(Profiles.PROFILE_TEST)
@Testcontainers
@SpringJUnitWebConfig
@AutoConfigureMockMvc
@SpringBootTest(classes = Application.class)
@ContextConfiguration(initializers = {IntegrationTest.Initializer.class})
public abstract class IntegrationTest {
private static final MSSQLServerContainer mssqlContainer;
//added this
private static final AtomicBoolean initDB = new AtomicBoolean(true);
static {
mssqlContainer = new MSSQLServerContainer()
.withInitScript("setup.sql"); //Creates users/permissions etc
mssqlContainer.start();
}
static class Initializer implements ApplicationContextInitializer {
@Override
public void initialize(final ConfigurableApplicationContext configurableApplicationContext) {
TestPropertyValues.of(
"spring.datasource.url=" + mssqlContainer.getJdbcUrl(),
//added this
"spring.datasource.initialization-mode=" + (initDB.get() ? "always" : "never"))
.applyTo(configurableApplicationContext.getEnvironment());
//added this
initDB.set(false);
}
}
}
Run Code Online (Sandbox Code Playgroud)
基本上,我将spring.datasource.initialization-mode设置为始终在第一次启动时设置,因为数据库尚未设置,然后将其重置为never对于此后的每个上下文初始化。因此,Spring 在第一次运行后不会尝试执行启动脚本。
效果很好,但我不喜欢在这里隐藏这个配置,所以仍然希望其他人能想出更好、更“按设计”的东西
归档时间: |
|
查看次数: |
5483 次 |
最近记录: |