-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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.