When writing a test with Testcontainers and Spring, the pattern is to use @DynamicPropertySource to register the container resource properties.

@SpringJUnitConfig(...)
@Testcontainers
class ExampleIntegrationTests {

     @Container
     static RedisContainer redis = new RedisContainer();

     // ...

     @DynamicPropertySource
     static void redisProperties(DynamicPropertyRegistry registry) {
         registry.add("redis.host", redis::getHost);
         registry.add("redis.port", redis::getMappedPort);
     }
 }

(Copied from @DynamicPropertySource javadoc)

This is ok for a single test class. However, while writing multiple such integration tests, in order to share the same containers, usually people create a parent base class. One reason is that @DynamicPropertySource needs to be in the test class hierarchy to register the resource info from the testcontainers. This enforces the inheritance pattern.

With this pattern, I often see that the parent class has many containers but individual child test classes only require a subset of containers.

class IntegrationTestsBase {
  @Container
  static RedisContainer redis = new RedisContainer();

  @Container
  static MySQLContainer mysql = new MySQLContainer();

  @Container
  static MyContainer custom = new MyContainer();

  @DynamicPropertySource
  static void register(DynamicPropertyRegistry registry) {
     ...
  }
}

class OnlyNeedRedisTests extends IntegrationTestsBase {
  // only use redis container
}

class OnlyNeedMySqlTests extends IntegrationTestsBase {
  // only use mysql container
}

Rather than inheritance, I prefer to have an annotation for each container resource.

For example, I wrote something like this to use either locally running mysql or testcontainer.

public class MyDbTestExtension implements BeforeAllCallback, AfterAllCallback {

    static final MySQLContainer mySQLContainer = new MySQLContainer<>("mysql:8");
    static final String localJdbcUrl = "jdbc:mysql://localhost:3306/my_db?user=root&password=password";

    private static final String SPRING_DB_PROPERTY = "spring.datasource.url";

    @Override
    public void beforeAll(ExtensionContext context) {
        try {
            // check local mysql is running or not
            DriverManager.getConnection(localJdbcUrl);
        }
        catch (Exception ex) {
            mySQLContainer.start();
        }

        // To encapsulate logic in extension, use system property to let spring pick up
        // the DB url. @DynamicPropertySource unfortunately only works within test class.
        System.setProperty(SPRING_DB_PROPERTY, getJdbcUrl());
    }

    public static String getJdbcUrl() {
        if (mySQLContainer.isRunning()) {
            return mySQLContainer.getJdbcUrl();
        }
        return localJdbcUrl;
    }

    @Override
    public void afterAll(ExtensionContext context) throws Exception {
        System.clearProperty(SPRING_DB_PROPERTY);
    }
}

In my Boot test, I simply extend it without parent class.

@DataJpaTest(...)
@ExtendWith(MyDbTestExtension.class)
class MyDbTests {
}

One problem of the above junit extension is that it is using System.setProperty to pass the testcontainer resource information to the Spring properties. It would be a problem to run such tests in parallel.

If there is a simple solution to pass the Spring property from JUnit extension to the test context, it could avoid using system properties.

One quick idea is that if there is a particular variable in ExtensionContext.Store that spring understands, it could be used to pass property key-values from an extension to the spring test context infrastructure.

Comment From: preceda

I'm looking for exactly the same solution. It would be really nice to have it.

Comment From: ttddyy

FYI, for testcontainers integration, I wrote a PoC with a different approach that uses ContextCustomizer to populate properties.(Kind of a variant of @DynamicPropertySource) https://github.com/ttddyy/demo-testcontainer-integration

Comment From: vpavic

Since both @Container field and @DynamicPropertySource method need to be static, couldn't you also use something along these lines:

@Testcontainers
@DirtiesContext
interface PostgreSqlIntegrationTest {

    @Container
    JdbcDatabaseContainer<?> postgresql = new PostgreSQLContainerProvider().newInstance("14.6-alpine");

    @DynamicPropertySource
    static void dataSourceProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", postgresql::getJdbcUrl);
        registry.add("spring.datasource.username", postgresql::getUsername);
        registry.add("spring.datasource.password", postgresql::getPassword);
    }

}

And then provide the equivalent interfaces for other containers you use within your project.

This removes the need to have a base integration test class with all the containers, as each integration test class can pick which ones to use by implementing the appropriate interface.

Comment From: snicoll

The support for ServiceConnection in Spring Boot has to a large extent made this request obsolete, at least in its original form. I don't really see how a JUnit extension could see the Spring environment, that looks a bit backwards to me.

However, manipulating the environment is possible, see SpringBootTestContextBootstrapper#processPropertySourceProperties for example.