Introduction

At some point, when I was using PostgreSQLContainer for integration tests I faced the next exception during a database migrations invocation:

Caused by: org.postgresql.util.PSQLException: Connection to localhost:<port> refused. 
Check that the hostname and port are correct and that the postmaster is accepting TCP/IP connections.

The error was intermittent, but eventually, it was discovered, that despite the fact that a database in the container is ready to accept connections, the container’s exposed port is not. It has been caused by the container environment that was used. The issue is quite common and takes a considerable amount of time to be identified and resolved.

I had to use HostPortWaitStrategy as a wait strategy for the container. But I also wanted to be 100% sure, that the database is ready to accept connections. So, the default PostgreSQLContainer wait strategy should have been applied as well. That is where a Testcontainers wait strategies combination helped me.

Below, you can find an example of PostgreSQLContainer configuration with multiple wait strategies.

Configuration class

In the configuration I use the WITH_MAXIMUM_OUTER_TIMEOUT mode as an example. Using this mode, the inner strategies wait with their own timeouts, but the WaitAllStrategy throws an exception, if its limit is reached (which is specified in withStartupTimeout). The other two modes are described comprehensively in Testcontainers Javadocs, so you can choose the one that suits you best.

package denshchikov.dmitry.taskcreator.config;

import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.containers.wait.strategy.WaitAllStrategy;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

import java.time.Duration;

import static org.testcontainers.containers.wait.strategy.WaitAllStrategy.Mode.WITH_MAXIMUM_OUTER_TIMEOUT;

@Testcontainers
public interface IntegrationTest {

  @Container
  PostgreSQLContainer<?> postgresContainer =
      new PostgreSQLContainer<>("postgres:latest")
          .waitingFor(
              new WaitAllStrategy(WITH_MAXIMUM_OUTER_TIMEOUT)
                  .withStartupTimeout(Duration.ofSeconds(90))
                  .withStrategy(
                      Wait.forLogMessage(
                          ".*database system is ready to accept connections.*\\s", 2))
                  .withStrategy(Wait.forListeningPort()));

  @DynamicPropertySource
  static void datasourceProperties(DynamicPropertyRegistry registry) {
    registry.add("spring.datasource.username", postgresContainer::getUsername);
    registry.add("spring.datasource.password", postgresContainer::getPassword);
    registry.add("spring.datasource.url", postgresContainer::getJdbcUrl);
  }
}

Usage Example

Then you can just implement the interface in your test classes. Here is an example with a Spring Boot test.

import denshchikov.dmitry.taskcreator.config.IntegrationTest;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class TaskServiceTest implements IntegrationTest {
    // ...
}

You can also find the full project in my repository. It contains a service class that should be tested, Flyway DB migrations, the configuration and the tests.