testcontainers / testcontainers/testcontainers-java
[Enhancement]: Containers declared with @Container can not depend od each other mapped ports using method withEnv(). Use Supplier<String> for env values
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 8.7k
- Forks
- 1.9k
- Avg merge
- 2d 17h
- Merged PRs (30d)
- 9
Description
Module
Core
Proposal
When configuring test case like:
@Container
public static final GenericContainer<?> ACTIVE_MQ_CONTAINER = new GenericContainer<>(ACTIVE_MQ_IMAGE)
.withExposedPorts(61616, 8161, 5672)
.withExtraHost("host.docker.internal", "host-gateway")
.withAccessToHost(true)
.withNetwork(NETWORK);
@Container
private static final GenericContainer<?> MOCK_SERVER = new GenericContainer<>(MOCK_SERVER_IMAGE)
.withExposedPorts(1080)
.withNetwork(NETWORK)
.withExtraHost("host.docker.internal", "host-gateway")
.withAccessToHost(true);
@Container
private static final GenericContainer MS_SQL_CONTAINER = new GenericContainer<>(MS_SQL_IMAGE)
.withEnv("ACCEPT_EULA", "Y")
.withEnv("SA_PASSWORD", "yourStrong(!)Password")
.withNetwork(NETWORK)
.withExposedPorts(1433)
.withNetworkAliases("base");
@Container
private static final GenericContainer<?> APP_SERVER = new GenericContainer<>(APP_IMAGE)
.dependsOn(MS_SQL_CONTAINER,MOCK_SERVER, ACTIVE_MQ_CONTAINER)
.withEnv("ConnectionStrings__AzureServiceBus", "amqp://host.docker.internal:" + ACTIVE_MQ_CONTAINER.getMappedPort(5672))
...
dependency will fail because the first three Testcontainers are not started and line:
.withEnv("ConnectionStrings__AzureServiceBus", "amqp://host.docker.internal:" + ACTIVE_MQ_CONTAINER.getMappedPort(5672))
will throw an error because can not read the value from the not-started container.
Automated init sequence when using @Container annotation can be replaced with:
private static final GenericContainer<?> FIRST_CONTAINER = new GenericContainer<>(
JUnitJupiterTestImages.HTTPD_IMAGE).withExposedPorts(80);
private static final GenericContainer<?> SECOND_CONTAINER = new GenericContainer<>(
JUnitJupiterTestImages.HTTPD_IMAGE).withExposedPorts(80);
private static final GenericContainer<?> APP_CONTAINER = new GenericContainer<>(JUnitJupiterTestImages.HTTPD_IMAGE)
.dependsOn(FIRST_CONTAINER, SECOND_CONTAINER).withExposedPorts(80)
.withEnv(FIRST_DEPENDENCY_ENV_KEY, String.valueOf(FIRST_CONTAINER.getFirstMappedPort()))
.withEnv(SECOND_DEPENDENCY_ENV_KEY, String.valueOf(SECOND_CONTAINER.getFirstMappedPort()));
@BeforeAll
public static void setupWithException() {
FIRST_CONTAINER.start();
SECOND_CONTAINER.start();
APP_CONTAINER.start();
A possible solution is to defer the resolution of dependency of ACTIVE_MQ_CONTAINER.getMappedPort(5672)) to a read stage of the dependent container (APP_SERVER container startup time).
Using Supplier<String> instead of String for type of value in Env Map. With this change, the example from above will work.
The current workaround is to start containers manually without @ Container annotation and using manually written checks 'is container started'. Containers in the test class must be declared in an ordered way.
Code snippet for manual container start-check:
public static final GenericContainer<?> ACTIVE_MQ_CONTAINER;
static {
ACTIVE_MQ_CONTAINER = new GenericContainer<>(ACTIVE_MQ_IMAGE)
.withExposedPorts(61616, 8161, 5672)
.withExtraHost("host.docker.internal", "host-gateway")
.withAccessToHost(true)
.withNetwork(NETWORK);
ACTIVE_MQ_CONTAINER.start();
}
static {
boolean started = false;
while (!started) {
try {
log.info("ACTIVE_MQ_CONTAINER.getFirstMappedPort(): " + ACTIVE_MQ_CONTAINER.getMappedPort(5672));
started = true;
} catch (Exception e) {
// nothing
log.info("in loop error");
}
}
}
alternative is:
private static final GenericContainer<?> FIRST_CONTAINER = new GenericContainer<>(
JUnitJupiterTestImages.HTTPD_IMAGE).withExposedPorts(80);
private static final GenericContainer<?> SECOND_CONTAINER = new GenericContainer<>(
JUnitJupiterTestImages.HTTPD_IMAGE).withExposedPorts(80);
private static final GenericContainer<?> APP_CONTAINER = new GenericContainer<>(JUnitJupiterTestImages.HTTPD_IMAGE)
.dependsOn(FIRST_CONTAINER, SECOND_CONTAINER).withExposedPorts(80);
@BeforeAll
public static void setupWithException() {
FIRST_CONTAINER.start();
SECOND_CONTAINER.start();
// read mapped ports after containers are started
APP_CONTAINER
.withEnv(FIRST_DEPENDENCY_ENV_KEY, String.valueOf(FIRST_CONTAINER.getFirstMappedPort()))
.withEnv(SECOND_DEPENDENCY_ENV_KEY, String.valueOf(SECOND_CONTAINER.getFirstMappedPort()));
APP_CONTAINER.start();
Expected behavior should be:
@Container
public static final GenericContainer<?> ACTIVE_MQ_CONTAINER = new GenericContainer<>(ACTIVE_MQ_IMAGE)
.withExposedPorts(61616, 8161, 5672);
@Container
private static final GenericContainer<?> MOCK_SERVER = new GenericContainer<>(MOCK_SERVER_IMAGE)
.withExposedPorts(1080);
@Container
private static final GenericContainer MS_SQL_CONTAINER = new GenericContainer<>(MS_SQL_IMAGE)
.withEnv("ACCEPT_EULA", "Y")
.withEnv("SA_PASSWORD", "yourStrong(!)Password")
.withNetwork(NETWORK)
.withExposedPorts(1433);
@Container
private static final GenericContainer<?> APP_SERVER = new GenericContainer<>(APP_IMAGE)
.dependsOn(MS_SQL_CONTAINER,MOCK_SERVER, ACTIVE_MQ_CONTAINER)
.withEnv("ConnectionStrings__AzureServiceBus", () -> "amqp://host.docker.internal:" + ACTIVE_MQ_CONTAINER.getMappedPort(5672))
...
Last line:
.withEnv("ConnectionStrings__AzureServiceBus", "amqp://host.docker.internal:" + ACTIVE_MQ_CONTAINER.getMappedPort(5672))
replaced by deferred value retrieval:
.withEnv("ConnectionStrings__AzureServiceBus", () -> "amqp://host.docker.internal:" + ACTIVE_MQ_CONTAINER.getMappedPort(5672))
ensures proper startup order when one container depends on some runtime value of another container.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with GenericContainer.withEnv and the @Container startup and dependency path described in the issue. Trace when environment values are read relative to dependsOn and mapped-port availability. Done means a Supplier-based environment value is resolved during dependent-container startup, allowing the shown example to work without manual starts.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- docker, java
- Domain
- testing
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100