[英]How to reuse Testcontainers between multiple SpringBootTests?
我正在使用带有 Spring Boot 的 TestContainers 来为这样的存储库运行单元测试:
@Testcontainers
@ExtendWith(SpringExtension.class)
@ActiveProfiles("itest")
@SpringBootTest(classes = RouteTestingCheapRouteDetector.class)
@ContextConfiguration(initializers = AlwaysFailingRouteRepositoryShould.Initializer.class)
@TestExecutionListeners(listeners = DependencyInjectionTestExecutionListener.class)
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Tag("docker")
@Tag("database")
class AlwaysFailingRouteRepositoryShould {
@SuppressWarnings("rawtypes")
@Container
private static final PostgreSQLContainer database =
new PostgreSQLContainer("postgres:9.6")
.withDatabaseName("database")
.withUsername("postgres")
.withPassword("postgres");
但是现在我有 14 个这样的测试,每次运行测试时都会启动一个新的 Postgres 实例。 是否可以在所有测试中重复使用同一个实例? Singleton 模式没有帮助,因为每个测试都会启动一个新的应用程序。
我还在.testcontainers.properties
和.withReuse(true)
中尝试过testcontainers.reuse.enable=true
,但这没有帮助。
如果您想拥有可重复使用的容器,则不能使用 JUnit Jupiter 注释@Container
。 此注释确保在每次测试后停止容器。
您需要的是 singleton 容器方法,并使用例如@BeforeAll
来启动您的容器。 即使您随后在多个测试中使用了 .start(),如果您在容器定义中同时使用.start()
.withReuse(true)
和您家中的以下.testcontainers.properties
文件选择可重用性,Testcontainers 也不会启动新容器目录:
testcontainers.reuse.enable=true
一个简单的示例可能如下所示:
@SpringBootTest
public class SomeIT {
public static GenericContainer postgreSQLContainer = new PostgreSQLContainer().
withReuse(true);
@BeforeAll
public static void beforeAll() {
postgreSQLContainer.start();
}
@Test
public void test() {
}
}
和另一个集成测试:
@SpringBootTest
public class SecondIT {
public static GenericContainer postgreSQLContainer = new PostgreSQLContainer().
withReuse(true);
@BeforeAll
public static void beforeAll() {
postgreSQLContainer.start();
}
@Test
public void secondTest() {
}
}
目前有一个PR 增加了关于这个的文档
我整理了一篇博文,详细解释了如何使用 Testcontainers 重用容器。
If you decide go forward with the singleton pattern , mind the warning in " Database containers launched via JDBC URL scheme ". 我花了几个小时才注意到,即使我使用的是singleton 模式,总是会创建一个额外的容器,映射到不同的端口。
In summary, do not use the test containers JDBC (host-less) URIs, such as jdbc:tc:postgresql:<image-tag>:///<databasename>
, if you need use the singleton pattern .
接受的答案很好,但问题是您仍然必须为每个集成测试重复配置(创建、启动等)。 最好使用更少的代码行进行更简单的配置。 我认为更干净的版本会使用 JUnit 5 个扩展。
这就是我解决问题的方法。 下面的示例使用 MariaDB 容器,但该概念适用于所有容器。
public class AppMariaDBContainer extends MariaDBContainer<AppMariaDBContainer> {
private static final String IMAGE_VERSION = "mariadb:10.5";
private static final String DATABASE_NAME = "my-db";
private static final String USERNAME = "user";
private static final String PASSWORD = "strong-password";
public static AppMariaDBContainer container = new AppMariaDBContainer()
.withDatabaseName(DATABASE_NAME)
.withUsername(USERNAME)
.withPassword(PASSWORD);
public AppMariaDBContainer() {
super(IMAGE_VERSION);
}
}
DataSource
属性。 并在需要时运行迁移:public class DatabaseSetupExtension implements BeforeAllCallback {
@Override
public void beforeAll(ExtensionContext context) {
AppMariaDBContainer.container.start();
updateDataSourceProps(AppMariaDBContainer.container);
//migration logic here (if needed)
}
private void updateDataSourceProps(AppMariaDBContainer container) {
System.setProperty("spring.datasource.url", container.getJdbcUrl());
System.setProperty("spring.datasource.username", container.getUsername());
System.setProperty("spring.datasource.password", container.getPassword());
}
}
@ExtendWith
到你的测试 class@SpringBootTest
@ExtendWith(MariaDBSetupExtension.class)
class ApplicationIntegrationTests {
@Test
void someTest() {
}
}
另一个测试
@SpringBootTest
@ExtendWith(MariaDBSetupExtension.class)
class AnotherIntegrationTests {
@Test
void anotherTest() {
}
}
使用 singleton 容器或可重用容器是可能的解决方案,但因为它们不是 scope 容器的生命周期与应用程序上下文的生命周期都不太理想。
但是,可以使用ContextCustomizerFactory
将容器 scope 到应用程序上下文生命周期, 我已经在博客文章中对此进行了更详细的介绍。
在测试中使用:
@Slf4j
@SpringBootTest
@EnabledPostgresTestContainer
class DemoApplicationTest {
@Test
void contextLoads() {
log.info("Hello world");
}
}
然后在META-INF/spring.factories
中启用注解:
org.springframework.test.context.ContextCustomizerFactory=\
com.logarithmicwhale.demo.EnablePostgresTestContainerContextCustomizerFactory
可以实现为:
public class EnablePostgresTestContainerContextCustomizerFactory implements ContextCustomizerFactory {
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface EnabledPostgresTestContainer {
}
@Override
public ContextCustomizer createContextCustomizer(Class<?> testClass,
List<ContextConfigurationAttributes> configAttributes) {
if (!(AnnotatedElementUtils.hasAnnotation(testClass, EnabledPostgresTestContainer.class))) {
return null;
}
return new PostgresTestContainerContextCustomizer();
}
@EqualsAndHashCode // See ContextCustomizer java doc
private static class PostgresTestContainerContextCustomizer implements ContextCustomizer {
private static final DockerImageName image = DockerImageName
.parse("postgres")
.withTag("14.1");
@Override
public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
var postgresContainer = new PostgreSQLContainer<>(image);
postgresContainer.start();
var properties = Map.<String, Object>of(
"spring.datasource.url", postgresContainer.getJdbcUrl(),
"spring.datasource.username", postgresContainer.getUsername(),
"spring.datasource.password", postgresContainer.getPassword(),
// Prevent any in memory db from replacing the data source
// See @AutoConfigureTestDatabase
"spring.test.database.replace", "NONE"
);
var propertySource = new MapPropertySource("PostgresContainer Test Properties", properties);
context.getEnvironment().getPropertySources().addFirst(propertySource);
}
}
}
我不确定@Testcontainers
是如何工作的,但我怀疑它可能会根据 class 工作。
Just make your singleton static as described in Singleton pattern and get it in every test from your signleton holder, don't define it in every test class.
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.