I am using currently TestContainers singleton pattern in order to reuse my db across all JVM tests and I ended up using an enum for it. So I was wondering if it was possible to extend the @DynamicPropertySource so it can be used at class level on something like this:

@DynamicPropertySource(TestContainers.POSTGRES::registerProperties)  // Or even, if it is valid, something like (r -> r.add("aaa","aa")))
public class MyFlowIT { ... } 

The updated annotation's "values"/default property (or a new explicit one) would have to be of type Consumer<DynamicPropertyRegistry> That would allow us to save from having to define the following in each test,

    @DynamicPropertySource
    static void postgresProperties(DynamicPropertyRegistry registry) {
        TestContainers.POSTGRES.registerProperties(registry);
    }

With my suggested way, we could also include the db setup as part of a meta-annotation with other annotations. Wouldn't rock something like this?:

@IntegrationTest   
public class MyFlowIT { ... }

where my custom annotation would be something like:

@DynamicPropertySource(TestContainers.POSTGRES::registerProperties) 
@TestInstance(LifeCycle.PER_CLASS)
@Sql(scripts = "/db/scenarios/common/CleanDB.sql", executionPhase = AFTER_TEST_METHOD)
@SqlMergeMode(SqlMergeMode.MergeMode.MERGE) 
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface IntegrationTest {}

Comment From: philwebb

I'm afraid this isn't possible due to the fact that annotations cannot declare Consumer attributes. See this section of the Java Language Spec.

Comment From: nightswimmings

@philwebb oh.. what about accepting an interface like

interface DynamicPropertyRegistrable {
    public void registerProperties(DynamicPropertyRegistry dynamicPropertyRegistry) ; 
}   

Then the enum would be something like:

public enum TestContainers implements DynamicPropertyRegistrable {

    POSTGRES ("POSTGRES", new PostgresInitializer()),
    KAFKA ("KAFKA", new KafkaInitializer()),
    ACTIVEMQ ("ACTIVEMQ", new ActiveMQInitializer()),
    OPENSEARCH("OPENSEARCH", new OpenSearchInitialiser());

    @Override
    public void registerProperties(DynamicPropertyRegistry dynamicPropertyRegistry) {
        this.propertyRegistrar.forEach((property,supplier) -> dynamicPropertyRegistry.add(property, supplier));
    }
(...)

Then we could use it as @DynamicPropertySource(TestContainers.POSTGRES). I would really love to save me from defining the register section at the begginning of each of my 300 ITs and annotation composition is less problematic and more elegant than abstracting in parents expressly

@DynamicPropertySource(TestContainers.POSTGRES) 
@TestInstance(LifeCycle.PER_CLASS)
@Sql(scripts = "/db/scenarios/common/CleanDB.sql", executionPhase = AFTER_TEST_METHOD)
@SqlMergeMode(SqlMergeMode.MergeMode.MERGE) 
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface IntegrationTest {}

Comment From: wilkinsona

@nightswimmings Unfortunately, that wouldn't work either. As described in the spec to which Phil linked, the attribute has to be declared as one of the following:

  • A primitive type
  • String
  • Class or an invocation of Class
  • An enum type
  • An annotation type
  • An array type whose component type is one of the preceding types

While your TestContainer type is an enum, it couldn't be used as the type of the annotation's attribute as @DynamicPropertySource knows nothing about it. Note that this is all academic from a Spring Boot perspective as @DynamicPropertySource is a Spring Framework feature.

Comment From: nightswimmings

Oops thanks for the clarification and sorry to get it wrong if it's a Spring Framework feature! I'll ask them!