Skip to content

Commit feb8abf

Browse files
committed
TestcontainersBeanRegistrationAotProcessor that replaces InstanceSupplier of Container by either direct field usage or a reflection equivalent.
If the field is private, the reflection will be used; otherwise, direct access to the field will be used DynamicPropertySourceBeanFactoryInitializationAotProcessor that generates methods for each annotated @DynamicPropertySource method
1 parent 9d6cfba commit feb8abf

File tree

5 files changed

+464
-5
lines changed

5 files changed

+464
-5
lines changed

spring-boot-project/spring-boot-testcontainers/src/dockerTest/java/org/springframework/boot/testcontainers/ImportTestcontainersTests.java

+173-2
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,29 @@
1818

1919
import java.lang.annotation.Retention;
2020
import java.lang.annotation.RetentionPolicy;
21+
import java.util.function.BiConsumer;
2122

2223
import org.junit.jupiter.api.AfterEach;
2324
import org.junit.jupiter.api.Test;
2425
import org.testcontainers.containers.Container;
26+
import org.testcontainers.containers.MongoDBContainer;
2527
import org.testcontainers.containers.PostgreSQLContainer;
2628

29+
import org.springframework.aot.test.generate.TestGenerationContext;
2730
import org.springframework.boot.testcontainers.beans.TestcontainerBeanDefinition;
2831
import org.springframework.boot.testcontainers.context.ImportTestcontainers;
32+
import org.springframework.boot.testcontainers.lifecycle.TestcontainersLifecycleApplicationContextInitializer;
2933
import org.springframework.boot.testsupport.container.DisabledIfDockerUnavailable;
3034
import org.springframework.boot.testsupport.container.TestImage;
35+
import org.springframework.context.ApplicationContextInitializer;
3136
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
37+
import org.springframework.context.aot.ApplicationContextAotGenerator;
38+
import org.springframework.context.support.GenericApplicationContext;
39+
import org.springframework.core.env.ConfigurableEnvironment;
40+
import org.springframework.core.test.tools.CompileWithForkedClassLoader;
41+
import org.springframework.core.test.tools.Compiled;
42+
import org.springframework.core.test.tools.TestCompiler;
43+
import org.springframework.javapoet.ClassName;
3244
import org.springframework.test.context.DynamicPropertyRegistry;
3345
import org.springframework.test.context.DynamicPropertySource;
3446

@@ -43,6 +55,8 @@
4355
@DisabledIfDockerUnavailable
4456
class ImportTestcontainersTests {
4557

58+
private final TestGenerationContext generationContext = new TestGenerationContext();
59+
4660
private AnnotationConfigApplicationContext applicationContext;
4761

4862
@AfterEach
@@ -102,7 +116,7 @@ void importWhenHasNonStaticContainerFieldThrowsException() {
102116
@Test
103117
void importWhenHasContainerDefinitionsWithDynamicPropertySource() {
104118
this.applicationContext = new AnnotationConfigApplicationContext(
105-
ContainerDefinitionsWithDynamicPropertySource.class);
119+
ImportWithoutValueWithDynamicPropertySource.class);
106120
assertThat(this.applicationContext.getEnvironment().containsProperty("container.port")).isTrue();
107121
}
108122

@@ -122,6 +136,119 @@ void importWhenHasBadArgsDynamicPropertySourceMethod() {
122136
.withMessage("@DynamicPropertySource method 'containerProperties' must be static");
123137
}
124138

139+
@Test
140+
@CompileWithForkedClassLoader
141+
void importTestcontainersImportWithoutValueAotContribution() {
142+
this.applicationContext = new AnnotationConfigApplicationContext();
143+
this.applicationContext.register(ImportWithoutValue.class);
144+
compile((freshContext, compiled) -> {
145+
PostgreSQLContainer<?> container = freshContext.getBean(PostgreSQLContainer.class);
146+
assertThat(container).isSameAs(ImportWithoutValue.container);
147+
});
148+
}
149+
150+
@Test
151+
@CompileWithForkedClassLoader
152+
void importTestcontainersImportWithValueAotContribution() {
153+
this.applicationContext = new AnnotationConfigApplicationContext();
154+
this.applicationContext.register(ImportWithValue.class);
155+
compile((freshContext, compiled) -> {
156+
PostgreSQLContainer<?> container = freshContext.getBean(PostgreSQLContainer.class);
157+
assertThat(container).isSameAs(ContainerDefinitions.container);
158+
});
159+
}
160+
161+
@Test
162+
@CompileWithForkedClassLoader
163+
void importTestcontainersImportWithoutValueWithDynamicPropertySourceAotContribution() {
164+
this.applicationContext = new AnnotationConfigApplicationContext();
165+
this.applicationContext.register(ImportWithoutValueWithDynamicPropertySource.class);
166+
compile((freshContext, compiled) -> {
167+
PostgreSQLContainer<?> container = freshContext.getBean(PostgreSQLContainer.class);
168+
assertThat(container).isSameAs(ImportWithoutValueWithDynamicPropertySource.container);
169+
assertThat(freshContext.getEnvironment().getProperty("container.port", Integer.class))
170+
.isEqualTo(ImportWithoutValueWithDynamicPropertySource.container.getFirstMappedPort());
171+
});
172+
}
173+
174+
@Test
175+
@CompileWithForkedClassLoader
176+
void importTestcontainersCustomPostgreSQLContainerDefinitionsAotContribution() {
177+
this.applicationContext = new AnnotationConfigApplicationContext();
178+
this.applicationContext.register(CustomPostgreSQLContainerDefinitions.class);
179+
compile((freshContext, compiled) -> {
180+
CustomPostgreSQLContainer container = freshContext.getBean(CustomPostgreSQLContainer.class);
181+
assertThat(container).isSameAs(CustomPostgreSQLContainerDefinitions.container);
182+
});
183+
}
184+
185+
@Test
186+
@CompileWithForkedClassLoader
187+
void importTestcontainersImportWithoutValueNotAccessibleContainerAndDynamicPropertySourceAotContribution() {
188+
this.applicationContext = new AnnotationConfigApplicationContext();
189+
this.applicationContext.register(ImportWithoutValueNotAccessibleContainerAndDynamicPropertySource.class);
190+
compile((freshContext, compiled) -> {
191+
MongoDBContainer container = freshContext.getBean(MongoDBContainer.class);
192+
assertThat(container).isSameAs(ImportWithoutValueNotAccessibleContainerAndDynamicPropertySource.container);
193+
assertThat(freshContext.getEnvironment().getProperty("mongo.port", Integer.class)).isEqualTo(
194+
ImportWithoutValueNotAccessibleContainerAndDynamicPropertySource.container.getFirstMappedPort());
195+
});
196+
}
197+
198+
@Test
199+
@CompileWithForkedClassLoader
200+
void importTestcontainersWithNotAccessibleContainerAndDynamicPropertySourceAotContribution() {
201+
this.applicationContext = new AnnotationConfigApplicationContext();
202+
this.applicationContext.register(ImportWithValueAndDynamicPropertySource.class);
203+
compile((freshContext, compiled) -> {
204+
PostgreSQLContainer<?> container = freshContext.getBean(PostgreSQLContainer.class);
205+
assertThat(container).isSameAs(ContainerDefinitionsWithDynamicPropertySource.container);
206+
assertThat(freshContext.getEnvironment().getProperty("postgres.port", Integer.class))
207+
.isEqualTo(ContainerDefinitionsWithDynamicPropertySource.container.getFirstMappedPort());
208+
});
209+
}
210+
211+
@Test
212+
@CompileWithForkedClassLoader
213+
void importTestcontainersMultipleContainersAndDynamicPropertySourcesAotContribution() {
214+
this.applicationContext = new AnnotationConfigApplicationContext();
215+
this.applicationContext.register(ImportWithoutValueNotAccessibleContainerAndDynamicPropertySource.class);
216+
this.applicationContext.register(ImportWithValueAndDynamicPropertySource.class);
217+
compile((freshContext, compiled) -> {
218+
MongoDBContainer mongo = freshContext.getBean(MongoDBContainer.class);
219+
PostgreSQLContainer<?> postgres = freshContext.getBean(PostgreSQLContainer.class);
220+
assertThat(mongo).isSameAs(ImportWithoutValueNotAccessibleContainerAndDynamicPropertySource.container);
221+
assertThat(postgres).isSameAs(ContainerDefinitionsWithDynamicPropertySource.container);
222+
ConfigurableEnvironment environment = freshContext.getEnvironment();
223+
assertThat(environment.getProperty("postgres.port", Integer.class))
224+
.isEqualTo(ContainerDefinitionsWithDynamicPropertySource.container.getFirstMappedPort());
225+
assertThat(environment.getProperty("mongo.port", Integer.class)).isEqualTo(
226+
ImportWithoutValueNotAccessibleContainerAndDynamicPropertySource.container.getFirstMappedPort());
227+
});
228+
}
229+
230+
@SuppressWarnings("unchecked")
231+
private void compile(BiConsumer<GenericApplicationContext, Compiled> result) {
232+
ClassName className = processAheadOfTime();
233+
TestCompiler.forSystem().with(this.generationContext).compile((compiled) -> {
234+
try (GenericApplicationContext context = new GenericApplicationContext()) {
235+
new TestcontainersLifecycleApplicationContextInitializer().initialize(context);
236+
ApplicationContextInitializer<GenericApplicationContext> initializer = compiled
237+
.getInstance(ApplicationContextInitializer.class, className.toString());
238+
initializer.initialize(context);
239+
context.refresh();
240+
result.accept(context, compiled);
241+
}
242+
});
243+
}
244+
245+
private ClassName processAheadOfTime() {
246+
ClassName className = new ApplicationContextAotGenerator().processAheadOfTime(this.applicationContext,
247+
this.generationContext);
248+
this.generationContext.writeGeneratedContent();
249+
return className;
250+
}
251+
125252
@ImportTestcontainers
126253
static class ImportWithoutValue {
127254

@@ -161,13 +288,25 @@ interface ContainerDefinitions {
161288

162289
}
163290

291+
private interface ContainerDefinitionsWithDynamicPropertySource {
292+
293+
@ContainerAnnotation
294+
PostgreSQLContainer<?> container = TestImage.container(PostgreSQLContainer.class);
295+
296+
@DynamicPropertySource
297+
static void containerProperties(DynamicPropertyRegistry registry) {
298+
registry.add("postgres.port", container::getFirstMappedPort);
299+
}
300+
301+
}
302+
164303
@Retention(RetentionPolicy.RUNTIME)
165304
@interface ContainerAnnotation {
166305

167306
}
168307

169308
@ImportTestcontainers
170-
static class ContainerDefinitionsWithDynamicPropertySource {
309+
static class ImportWithoutValueWithDynamicPropertySource {
171310

172311
static PostgreSQLContainer<?> container = TestImage.container(PostgreSQLContainer.class);
173312

@@ -196,4 +335,36 @@ void containerProperties() {
196335

197336
}
198337

338+
@ImportTestcontainers
339+
static class CustomPostgreSQLContainerDefinitions {
340+
341+
private static final CustomPostgreSQLContainer container = new CustomPostgreSQLContainer();
342+
343+
}
344+
345+
static class CustomPostgreSQLContainer extends PostgreSQLContainer<CustomPostgreSQLContainer> {
346+
347+
CustomPostgreSQLContainer() {
348+
super("postgres:14");
349+
}
350+
351+
}
352+
353+
@ImportTestcontainers
354+
static class ImportWithoutValueNotAccessibleContainerAndDynamicPropertySource {
355+
356+
private static final MongoDBContainer container = TestImage.container(MongoDBContainer.class);
357+
358+
@DynamicPropertySource
359+
private static void containerProperties(DynamicPropertyRegistry registry) {
360+
registry.add("mongo.port", container::getFirstMappedPort);
361+
}
362+
363+
}
364+
365+
@ImportTestcontainers(ContainerDefinitionsWithDynamicPropertySource.class)
366+
static class ImportWithValueAndDynamicPropertySource {
367+
368+
}
369+
199370
}

0 commit comments

Comments
 (0)