1818
1919import java .lang .annotation .Retention ;
2020import java .lang .annotation .RetentionPolicy ;
21+ import java .util .function .BiConsumer ;
2122
2223import org .junit .jupiter .api .AfterEach ;
2324import org .junit .jupiter .api .Test ;
2425import org .testcontainers .containers .Container ;
26+ import org .testcontainers .containers .MongoDBContainer ;
2527import org .testcontainers .containers .PostgreSQLContainer ;
2628
29+ import org .springframework .aot .test .generate .TestGenerationContext ;
2730import org .springframework .boot .testcontainers .beans .TestcontainerBeanDefinition ;
2831import org .springframework .boot .testcontainers .context .ImportTestcontainers ;
32+ import org .springframework .boot .testcontainers .lifecycle .TestcontainersLifecycleApplicationContextInitializer ;
2933import org .springframework .boot .testsupport .container .DisabledIfDockerUnavailable ;
3034import org .springframework .boot .testsupport .container .TestImage ;
35+ import org .springframework .context .ApplicationContextInitializer ;
3136import 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 ;
3244import org .springframework .test .context .DynamicPropertyRegistry ;
3345import org .springframework .test .context .DynamicPropertySource ;
3446
4355@ DisabledIfDockerUnavailable
4456class 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