Search code examples
javaspringtestcontainersr2dbc

How to reuse a `testcontainers` container with two different drivers?


I'm using both r2dbc and Liquibase in the same application. However, Liquibase is not able to run migrations using r2dbc so I need to use a separate jdbc driver just for it.

I followed the solution here, and used testcontainers for testing, so my application-test.yaml looks exactly like this:

spring:
  liquibase:
    url: jdbc:tc:postgresql:14.1:///testdb
  r2dbc:
    url: r2dbc:tc:postgresql:///testdb?TC_IMAGE_TAG=14.1

This works perfectly, migrations are launched and then the queries are able to run. The problem is, that this starts two different containers! So the migrations are run against one of them, and the queries against the other, and thus they find the database empty.

Is there any way I can tell testcontainers to use the same container for both connections.


Solution

  • When you use Testcontainers' JDBC support, which you configure by adding tc in the jdbc url, the lifecycle of the container is managed automatically. Since you have two different urls instrumented like that, you get 2 containers.

    Instead you can choose a different way to manage the lifecycle that gives you more control.

    You can either do it yourself by creating a containers instances and calling start()/stop() or for example use JUnit integration which will correspond containers lifecycle with the tests lifecycle.

    For example for JUnit5, you mark you class with @Testcontainers and the fields with @Container, something like:

    @Testcontainers
    class MixedLifecycleTests {
        @Container
        private static PostgreSQLContainer postgresqlContainer = new PostgreSQLContainer();
    }
    

    Since you're working on a Spring application you want to configure it to use the container, for that use @DynamicPropertySource: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/context/DynamicPropertySource.html

    In a nutshell, you mark a method with it and inside it configure Spring to use the databases in the container:

    @DynamicPropertySource
    static void redisProperties(DynamicPropertyRegistry registry) {
      registry.add("spring.datasource.url", postgresqlContainer:: getJdbcUrl);
      registry.add("spring.datasource.username", postgresqlContainer::getUsername);
      registry.add("spring.datasource.password", postgresqlContainer::getPassword);
    
      registry.add("spring.r2dbc.url", () -> "r2dbc:postgresql://"
                    + postgreSQLContainer.getHost() + ":" + postgreSQLContainer.getFirstMappedPort()
                    + "/" + postgreSQLContainer.getDatabaseName());
      registry.add("spring.r2dbc.username", postgreSQLContainer::getUsername);
      registry.add("spring.r2dbc.password", postgreSQLContainer::getPassword);
    }
    

    Note that since your app uses r2dbc and Liquibase works with non-reactive jdbc you should configure both.