1
ActiveProfiles("test")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@ContextConfiguration(initializers = AbstractIT.DockerPostgreDataSourceInitializer.class)
@Testcontainers
public abstract class AbstractIT {
    

    @SuppressWarnings("resource")
    @Container
    static PostgreSQLContainer<?> postgreSQLContainer = new PostgreSQLContainer<>("postgres:14")
            .withUsername("postgres")
            .withPassword("postgres")
            .withInitScript("sql/init.sql")
            .withDatabaseName("test")
            .withReuse(true);
    
    static {
        postgreDBContainer.start();
    }
    
    public static class DockerPostgreDataSourceInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
        
        @Override
        public void initialize(ConfigurableApplicationContext applicationContext) {
            
            TestPropertySourceUtils.addInlinedPropertiesToEnvironment(
                    applicationContext,
                    "spring.datasource.url=" + postgreDBContainer.getJdbcUrl(),
                    "spring.datasource.username=" + postgreDBContainer.getUsername(),
                    "spring.datasource.password=" + postgreDBContainer.getPassword()
            );
        }
    }
}

SqlExceptionHelper : HikariPool-1 - Connection is not available, request timed out after 30000ms. o.h.engine.jdbc.spi.SqlExceptionHelper : Connection to localhost:49168 refused. Check that the hostname and port are correct and that the postmaster is accepting TCP/IP connections.

I created one instance of Textcontainers, I have a lot of integration tests, but only one test class is working, the rest cannot. because the connection is being closed.

Installing @DirtiesContext and changing the open connection interval does not solve the problem.

Decision.

delete an annotation @Container.


 static PostgreSQLContainer<?> postgreSQLContainer = new PostgreSQLContainer<> ......

However, this solution will not allow you to work with the database. Because when one test class will work. Then there will be an error that the table.... exists. That is, the test container will try to initiate the table creation script again. How can I make it so that I can reuse the test container (not create a new one) and at the same time, I can use one database initialization script for all test nodes?

skyho
  • 1,438
  • 2
  • 20
  • 47

2 Answers2

0

application-test.yaml

spring:
  jpa:
    show-sql: true
    hibernate:
      ddl-auto: none
    open-in-view: false
    properties:
      hibernate:
        default-schema: public
        jdbc:
          lob.non_contextual_creation: true # Fix Postgres JPA Error (Method org.postgresql.jdbc.PgConnection.createClob() is not yet implemented).
          time_zone: UTC
          # to avoid name conflict of custom table objects
          # with object names in PostgresSQL
      auto_quote_keyword: true

  test:
    database:
      replace: none
  • baseClass
@ActiveProfiles("test")
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@ContextConfiguration(initializers =
        SpringDataTestcontainerTests
                .DockerPostgresDataInitializer.class)
public abstract class SpringDataTestcontainerTests {

    static PostgreSQLContainer<?> postgreSQLContainer;

    static {
        postgreSQLContainer = new PostgreSQLContainer<>("postgres:14")
              .withUsername("postgres")
               .withPassword("postgres")
                .withInitScript("sql/init.sql")
               .withDatabaseName("test")
                .withReuse(true);


        postgreSQLContainer.start();
    }

    public static class DockerPostgresDataInitializer
            implements ApplicationContextInitializer<ConfigurableApplicationContext> {

        String jdbcUrl = "spring.datasource.url=" + postgreSQLContainer.getJdbcUrl();
        String username = "spring.datasource.username=" + postgreSQLContainer.getUsername();
        String password = "spring.datasource.password=" + postgreSQLContainer.getPassword();

        @Override
        public void initialize(@NotNull ConfigurableApplicationContext applicationContext) {

            TestPropertySourceUtils
                    .addInlinedPropertiesToEnvironment(applicationContext, jdbcUrl, username, password);
        }
    }

    @Autowired
    protected OrderCreateService orderCreateService;

    @Autowired
    protected OrderReadService orderReadService;

    @Autowired
    protected OrderRepository orderRepository;


    @Test
    void contextLoads() throws SQLException {

        ResultSet resultSet = performQuery(postgreSQLContainer);
        resultSet.next();
        int result = resultSet.getInt(1);
        assertEquals(1, result);

        Assertions.assertThat(postgreSQLContainer.isRunning()).isTrue();
    }

    private ResultSet performQuery(@SuppressWarnings("rawtypes") PostgreSQLContainer postgreSQLContainer)
            throws SQLException {

        String query = "SELECT 1";

        String jdbcUrl = postgreSQLContainer.getJdbcUrl();
        String username = postgreSQLContainer.getUsername();
        String password = postgreSQLContainer.getPassword();

        Connection conn = DriverManager.getConnection(jdbcUrl, username, password);
        return conn.createStatement().executeQuery(query);
    }

}

Each test node starts with :

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class ServiceTest extends SpringDataTestcontainerTests {
}

skyho
  • 1,438
  • 2
  • 20
  • 47
0

When using the @Testcontainers and @Container annotation, JUnit-Jupiter will handle the container lifecycle, no need to call start() then. The datasource configuration should be injected into the Spring context using the DynamicPropertySource mechanism.

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
public abstract class AbstractIT {
    

    @SuppressWarnings("resource")
    @Container
    static PostgreSQLContainer<?> postgreSQLContainer = new PostgreSQLContainer<>("postgres:14")
            .withUsername("postgres")
            .withPassword("postgres")
            .withInitScript("sql/init.sql")
            .withDatabaseName("test")
            .withReuse(true);
    
     @DynamicPropertySource
     static void setupProperties(DynamicPropertyRegistry registry) {
         registry.add("spring.datasource.url", postgreSQLContainer ::getJdbcUrl);
         registry.add("spring.datasource.username", postgreSQLContainer ::getUsername);
         registry.add("spring.datasource.password", postgreSQLContainer ::getPassword);
     }

}
Kevin Wittek
  • 1,369
  • 9
  • 26
  • I done that. It doesn't work : $DockerPostgresDataSourceInitializer]: Constructor threw exception; nested exception is java.lang.IllegalStateException: Mapped port can only be obtained after the container is started – skyho Jun 15 '22 at 16:08
  • I've removed some inappropriate annotations from the original code and updated the example, you can give it another try. If this is not working, I suggest removing the `@Testcontainers` and `@Container` annotation and calling `start()` in the `DynamicPropertySource` method. – Kevin Wittek Jun 17 '22 at 13:46
  • I have a mistake -> Caused by: org.postgresql.util.PSQLException: ERROR: relation "orders" already exists. I put postgresSQLContainer.start(); in DynamicPropertySource in the method. And I have second mistake - https://stackoverflow.com/questions/72626372/ryuk-container-that-is-started-by-test-container-store-dont-stop-the-singleton – skyho Jun 20 '22 at 08:10
  • This error seems unrelated to Testcontainers and is about initial schema creation. – Kevin Wittek Jun 20 '22 at 10:45