Skip to content

Commit a68a884

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 4718485 commit a68a884

File tree

5 files changed

+446
-5
lines changed

5 files changed

+446
-5
lines changed

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

+152-2
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,27 @@
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;
2526
import org.testcontainers.containers.PostgreSQLContainer;
2627

28+
import org.springframework.aot.test.generate.TestGenerationContext;
2729
import org.springframework.boot.testcontainers.beans.TestcontainerBeanDefinition;
2830
import org.springframework.boot.testcontainers.context.ImportTestcontainers;
31+
import org.springframework.boot.testcontainers.lifecycle.TestcontainersLifecycleApplicationContextInitializer;
2932
import org.springframework.boot.testsupport.container.DisabledIfDockerUnavailable;
3033
import org.springframework.boot.testsupport.container.TestImage;
34+
import org.springframework.context.ApplicationContextInitializer;
3135
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
36+
import org.springframework.context.aot.ApplicationContextAotGenerator;
37+
import org.springframework.context.support.GenericApplicationContext;
38+
import org.springframework.core.test.tools.CompileWithForkedClassLoader;
39+
import org.springframework.core.test.tools.Compiled;
40+
import org.springframework.core.test.tools.TestCompiler;
41+
import org.springframework.javapoet.ClassName;
3242
import org.springframework.test.context.DynamicPropertyRegistry;
3343
import org.springframework.test.context.DynamicPropertySource;
3444

@@ -43,6 +53,8 @@
4353
@DisabledIfDockerUnavailable
4454
class ImportTestcontainersTests {
4555

56+
private final TestGenerationContext generationContext = new TestGenerationContext();
57+
4658
private AnnotationConfigApplicationContext applicationContext;
4759

4860
@AfterEach
@@ -102,7 +114,7 @@ void importWhenHasNonStaticContainerFieldThrowsException() {
102114
@Test
103115
void importWhenHasContainerDefinitionsWithDynamicPropertySource() {
104116
this.applicationContext = new AnnotationConfigApplicationContext(
105-
ContainerDefinitionsWithDynamicPropertySource.class);
117+
ImportWithoutValueWithDynamicPropertySource.class);
106118
assertThat(this.applicationContext.getEnvironment().containsProperty("container.port")).isTrue();
107119
}
108120

@@ -122,6 +134,100 @@ void importWhenHasBadArgsDynamicPropertySourceMethod() {
122134
.withMessage("@DynamicPropertySource method 'containerProperties' must be static");
123135
}
124136

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

@@ -161,13 +267,25 @@ interface ContainerDefinitions {
161267

162268
}
163269

270+
private interface ContainerDefinitionsWithDynamicPropertySource {
271+
272+
@ContainerAnnotation
273+
PostgreSQLContainer<?> container = TestImage.container(PostgreSQLContainer.class);
274+
275+
@DynamicPropertySource
276+
static void containerProperties(DynamicPropertyRegistry registry) {
277+
registry.add("container.port", container::getFirstMappedPort);
278+
}
279+
280+
}
281+
164282
@Retention(RetentionPolicy.RUNTIME)
165283
@interface ContainerAnnotation {
166284

167285
}
168286

169287
@ImportTestcontainers
170-
static class ContainerDefinitionsWithDynamicPropertySource {
288+
static class ImportWithoutValueWithDynamicPropertySource {
171289

172290
static PostgreSQLContainer<?> container = TestImage.container(PostgreSQLContainer.class);
173291

@@ -196,4 +314,36 @@ void containerProperties() {
196314

197315
}
198316

317+
@ImportTestcontainers
318+
static class CustomPostgreSQLContainerDefinitions {
319+
320+
private static final CustomPostgreSQLContainer container = new CustomPostgreSQLContainer();
321+
322+
}
323+
324+
static class CustomPostgreSQLContainer extends PostgreSQLContainer<CustomPostgreSQLContainer> {
325+
326+
CustomPostgreSQLContainer() {
327+
super("postgres:14");
328+
}
329+
330+
}
331+
332+
@ImportTestcontainers
333+
static class ImportWithoutValueNotAccessibleContainerAndDynamicPropertySource {
334+
335+
private static final PostgreSQLContainer<?> container = TestImage.container(PostgreSQLContainer.class);
336+
337+
@DynamicPropertySource
338+
private static void containerProperties(DynamicPropertyRegistry registry) {
339+
registry.add("container.port", container::getFirstMappedPort);
340+
}
341+
342+
}
343+
344+
@ImportTestcontainers(ContainerDefinitionsWithDynamicPropertySource.class)
345+
static class ImportWithValueAndDynamicPropertySource {
346+
347+
}
348+
199349
}

0 commit comments

Comments
 (0)