I'm developing a service that has many dependencies like Redis, PubSub, S3, ServiceC and ServiceD. When I hit an endpoint in development, e.g. /data/4
, a http request to ServiceC is performed.
So to that to work, something that mocks ServiceC must run. In my case it's wiremock (since I cant run ServiceC directly, as it also has many dependencies). All of these MockServices are in a docker-compose-dev file.
Now to my question: How can I run the docker-compose with testcontainers, get the assigned ports, and set the correct properties to the WebClient
has the right mock url + port?
What lifecycle hook can I use to run before spring boot starts, and also can configure the properties?
One downside would be an increased boot time in the dev mode though, but I can't assign fixed ports in docker compose file, because they might be used on the developer's machine. So makes no sense to ship url defaults for the service urls on localhost.
Testcontainers supports starting Docker Compose out-of-the-box using the DockerComposeContainer
class. While creating an instance of this class you have to expose all your containers:
@Testcontainers
public class DockerComposeTest {
@Container
public static DockerComposeContainer<?> environment =
new DockerComposeContainer<>(new File("docker-compose.yml"))
.withExposedService("database_1", 5432, Wait.forListeningPort())
.withExposedService("keycloak_1", 8080,
Wait.forHttp("/auth").forStatusCode(200)
.withStartupTimeout(Duration.ofSeconds(30)));
@Test
void dockerComposeTest() {
System.out.println(environment.getServicePort("database_1", 5432));
System.out.println(environment.getServicePort("keycloak_1", 8080));
}
}
The example above maps a PostgreSQL database and Keycloak to a random ephemeral port on your machine. Right after the wait checks pass, your test will run and you can access the actual port with .getServicePort()
.
A possible solution for the WebClient
configuration is to use a ApplicationContextInitializer
as you somehow have to define the Spring Boot property before the container starts:
public class PropertyInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
// static access to environment variable of the test
TestPropertyValues
.of("your_web_client_base_url=http://localhost:" + environment.getServicePort("serviceC", 8080) + "/api")
.applyTo(configurableApplicationContext);
}
}
You can then register the initializer for your integration tests with:
@Testcontainers
@ContextConfiguration(initializers = {PropertyInitializer.class})
class DockerComposeTest {
}
As an alternative, you could also try to solely mock all HTTP communication to external services with WireMock.