Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Testcontainers @DynamicPropertySource and @Container execution order doesn't work properly #40937

Closed
fantianwithmimo opened this issue May 29, 2024 · 2 comments
Assignees
Labels
status: declined A suggestion or change that we don't feel we should currently apply

Comments

@fantianwithmimo
Copy link

fantianwithmimo commented May 29, 2024

Hey team, we have met an issue when we use spring-boot-testcontainers 3.3.0.

The Problem:

2024-05-29T13:11:55.056+10:00 WARN 25652 --- [ main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: java.lang.IllegalStateException: Mapped port can only be obtained after the container is started

How to reproduce it:

demo-project.zip
Please us the demo-project and directly run RunApplicationLocally.kt

What we have found:

If we use spring-boot-testcontainers 3.2.0, all good.

2024-05-29T13:30:55.245+10:00 INFO 26680 --- [ main] tc.localstack/localstack:3.4.0 : Creating container for image: localstack/localstack:3.4.0
2024-05-29T13:30:55.291+10:00 INFO 26680 --- [ main] o.t.utility.RegistryAuthLocator : Credential helper/store (docker-credential-osxkeychain) does not have credentials for https://index.docker.io/v1/
2024-05-29T13:30:55.805+10:00 INFO 26680 --- [ main] tc.localstack/localstack:3.4.0 : Container localstack/localstack:3.4.0 is starting: ff868fd07f14190e93a3d4c31eedfe20c3806035dc2bbcf430a523dd7220f807
2024-05-29T13:31:01.686+10:00 INFO 26680 --- [ main] tc.localstack/localstack:3.4.0 : Container localstack/localstack:3.4.0 started in PT6.441141S
Add properties with dynamic values to the Environment for integration tests!
http://127.0.0.1:32831
us-east-1

If we use spring-boot-testcontainers 3.2.1 or later versions, it fails.

Add properties with dynamic values to the Environment for integration tests!
2024-05-29T13:32:49.997+10:00 WARN 26764 --- [ main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: java.lang.IllegalStateException: Mapped port can only be obtained after the container is started
2024-05-29T13:32:50.000+10:00 INFO 26764 --- [ main] .s.b.a.l.ConditionEvaluationReportLogger :

Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2024-05-29T13:32:50.012+10:00 ERROR 26764 --- [ main] o.s.boot.SpringApplication : Application run failed

java.lang.IllegalStateException: Mapped port can only be obtained after the container is started

Could you help us fix it up or any other way we could work it around?

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label May 29, 2024
@quaff
Copy link
Contributor

quaff commented May 29, 2024

You can try annotating @org.testcontainers.junit.jupiter.Testcontainers on class WithContainers.

But it will trigger another problem, the @DynamicPropertySource method is invoked twice from DynamicPropertiesContextCustomizer and DynamicPropertySourceMethodsImporter.

Thread [main] (Suspended (breakpoint at line 22 in TestContainerTests))	
	owns: DefaultContextCache  (id=82)	
	TestContainerTests.populateDynamicPropertyRegistry(DynamicPropertyRegistry) line: 22	
	NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]	
	NativeMethodAccessorImpl.invoke(Object, Object[]) line: 77	
	DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 43	
	Method.invoke(Object, Object...) line: 568	
	ReflectionUtils.invokeMethod(Method, Object, Object...) line: 281	
	DynamicPropertiesContextCustomizer.lambda$buildDynamicPropertiesMap$3(DynamicPropertyRegistry, Method) line: 82	
Thread [main] (Suspended (breakpoint at line 22 in TestContainerTests))	
	owns: DefaultContextCache  (id=82)	
	TestContainerTests.populateDynamicPropertyRegistry(DynamicPropertyRegistry) line: 22	
	NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]	
	NativeMethodAccessorImpl.invoke(Object, Object[]) line: 77	
	DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 43	
	Method.invoke(Object, Object...) line: 568	
	ReflectionUtils.invokeMethod(Method, Object, Object...) line: 281	
	DynamicPropertySourceMethodsImporter.lambda$registerDynamicPropertySources$0(DynamicPropertyRegistry, Method) line: 57	
	0x000000012f319850.accept(Object) line: not available	

Here is the test case:

package com.example.demo;

import org.junit.jupiter.api.Test;
import org.springframework.boot.testcontainers.context.ImportTestcontainers;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

@SpringJUnitConfig
//@Testcontainers
public class TestContainerTests {

	@Container
	static PostgreSQLContainer<?> container = new PostgreSQLContainer<>("postgres");

	@DynamicPropertySource
	static void populateDynamicPropertyRegistry(DynamicPropertyRegistry registry) {
		System.out.println(container.getMappedPort(5432));
	}

	@Test
	void test() {
	}

	@Configuration
	@ImportTestcontainers(TestContainerTests.class)
	static class Config {

	}

}

@wilkinsona wilkinsona self-assigned this May 29, 2024
@wilkinsona
Copy link
Member

The problem is that you're accessing the container directly within your @DynamicPropertySource method:

println(localStackContainer.getEndpointOverride(SQS))

This requires the container to have been started when the @DynamicPropertySource method is called, undoing the benefit of the registry's lazy nature where you register a property with a supplier for its value. Using a supplier means that the container does not have to have been started until the property is accessed which happens later in the application lifecycle.

This worked in 3.2.0 as the containers were started very early but that was at the expense of support for parallel startup being broken. This was fixed in #38831.

In summary, this only worked in 3.2.0 accidentally and, unfortunately, you'll have to make some changes to adapt to the way things now work. You can either remove println calls that access the container or move them into the supplier implementations where the values for the properties are being returned.

@wilkinsona wilkinsona closed this as not planned Won't fix, can't repro, duplicate, stale May 29, 2024
@wilkinsona wilkinsona added status: declined A suggestion or change that we don't feel we should currently apply and removed status: waiting-for-triage An issue we've not yet triaged labels May 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: declined A suggestion or change that we don't feel we should currently apply
Projects
None yet
Development

No branches or pull requests

4 participants