Search code examples
spring-boottestcontainers

How to setup local dev environment properties of Spring Boot based on docker compose with testcontainers?


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.


Solution

  • 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.