如何在多个SpringBootTests之间重用测试容器?

pgx2nnw8  于 2022-12-18  发布在  Spring
关注(0)|答案(5)|浏览(459)

我使用TestContainers with Sping Boot 来运行存储库的单元测试,如下所示:

@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 pattern没有帮助,因为每个测试都启动一个新的应用程序。
我还在.testcontainers.properties.withReuse(true)中尝试了testcontainers.reuse.enable=true,但没有帮助。

aurhwmvo

aurhwmvo1#

如果你想拥有可重用的容器,你不能使用JUnit Jupiter注解@Container,这个注解确保停止容器after each test
您需要的是单例容器方法,例如使用@BeforeAll来启动您的容器。即使您在多个测试中使用.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 that adds documentation
我写了一篇博客文章详细解释了how to reuse containers with Testcontainers

flvlnr44

flvlnr442#

如果您决定继续使用单例模式,请注意“通过JDBCURL方案启动的数据库容器”中的警告。我花了几个小时才注意到,即使我使用的是单例模式,也总是会创建Map到不同端口的额外容器。
总之,如果需要使用单例模式,请不要使用测试容器JDBC(无主机)URI,如jdbc:tc:postgresql:<image-tag>:///<databasename>

hiz5n14c

hiz5n14c3#

可以接受的答案很好,但问题是您仍然必须为每个集成测试重复配置(创建、启动等)。使用更少的代码行进行更简单的配置会更好。我认为更干净的版本应该使用JUnit 5扩展。
这就是我解决这个问题的方法。下面的例子使用了MariaDB容器,但是这个概念适用于所有的容器。
1.创建容器配置保存类:

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);
    }

}

1.创建一个扩展类来启动容器并设置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());
    }

}

1.将@ExtendWith添加到测试类

@SpringBootTest
@ExtendWith(MariaDBSetupExtension.class)
class ApplicationIntegrationTests {

    @Test
    void someTest() {
    }

}


又一个测试

@SpringBootTest
@ExtendWith(MariaDBSetupExtension.class)
class AnotherIntegrationTests {

    @Test
    void anotherTest() {
    }

}
goucqfw6

goucqfw64#

使用单例容器或可重用容器都是可能的解决方案,但因为它们没有将容器的生命周期限定在应用程序上下文的生命周期内,所以两者都不是理想的解决方案。
但是,可以通过使用ContextCustomizerFactoryI've written about this in more detail in a blog post将容器的范围限定为应用程序上下文生命周期。
在测试使用中:

@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);
        }

    }

}

q5lcpyga

q5lcpyga5#

我不确定@Testcontainers是如何工作的,但我怀疑它可能对每个类都有效。
只要按照Singleton pattern中的描述使你的单例静态化,并在每个测试中从你的单例保持器那里得到它,不要在每个测试类中定义它。

相关问题