Skip to content

spring-boot-testcontainers when used on abstract class creates multiple contexts #42854

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

Closed
wyhasany opened this issue Oct 23, 2024 · 4 comments
Closed
Labels
status: duplicate A duplicate of another issue

Comments

@wyhasany
Copy link

Creating integration tests with a shared abstract class to avoid initializing multiple contexts does not work as expected when a container is managed by Spring:

@SpringBootTest
@Testcontainers
abstract class DemoApplicationTests {

	@Autowired
	ClientRepository clientRepository;

	@Container
	@ServiceConnection
	static MongoDBContainer mongoDbContainer = new MongoDBContainer(DockerImageName.parse("mongo:latest"));

	@Test
	void contextLoads() {
	}
}

class FirstSubclass extends DemoApplicationTests {

	@Test
	void test() {
		Client client = new Client(UUID.randomUUID(), "John Doe", "A description");
		clientRepository.save(client);

		Optional<Client> foundClient = clientRepository.findById(client.id());
		then(foundClient).isPresent().get().extracting(Client::name).isEqualTo("John Doe");
	}
}

class SecondSubclass extends DemoApplicationTests {

	@Test
	void test() {
		Client client = new Client(UUID.randomUUID(), "John Doe", "A description");
		clientRepository.save(client);

		Optional<Client> foundClient = clientRepository.findById(client.id());
		then(foundClient).isPresent().get().extracting(Client::name).isEqualTo("John Doe");
	}
}

When running these tests, two application contexts are created, causing the container to be restarted. The second test then tries to connect to the container, which was already stopped after the first test.

To reproduce the issue, run the following command on the attached project:

./gradlew test

You can find the sample project here: demo.zip

Let me know if you'd like any further clarifications.

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

nosan commented Oct 23, 2024

Hey, @wyhasany

Thank you very much for your sample.

Your tests fail because TestcontainersExtension restarts mongodb container between
FirstSubclass.test() and SecondSubclass.test().

The issue is that TestcontainersExtension found the shared container mongoDbContainer and then stored it in the JUnit
Store as a CloseableResource. Since two different classes are involved, JUnit triggers the afterAll() callback
and then clears the store. Since the container was stored as a CloseableResource, JUnit also invokes the close() method,
which results in stopping the container.

How can you fix this?

Option 1

@SpringBootTest
@ImportTestcontainers
//@Testcontainers also could be used if needed, but you can't annotate the container with @Container annotation
abstract class DemoApplicationTests {

	@ServiceConnection
	static MongoDBContainer mongoDbContainer = new MongoDBContainer(DockerImageName.parse("mongo:latest"));

	//	...
}

Option 2

@SpringBootTest
@Import(TestcontainersConfiguration.class)
abstract class DemoApplicationTests {

}
@TestConfiguration(proxyBeanMethods = false)
class TestcontainersConfiguration {
	@Bean
	@ServiceConnection
	MongoDBContainer mongoDbContainer() {
		return new MongoDBContainer(DockerImageName.parse("mongo:latest"));
	}
}

@wilkinsona
Copy link
Member

When running these tests, two application contexts are created

I don't think that's the case. Only one context is created due to the test context Framework's caching as each test class has identical configuration.

causing the container to be restarted

As @nosan has explained above, it's @Testcontainers that is managing the container lifecycle and stopping it in the after all processing of the first test.

This is a duplicate of #38237. In addition to @nosan's suggestions, you can also avoid the problem by applying @DirtiesContext to DemoApplicationTests or by setting spring.test.context.cache.maxSize=1 in a spring.properties file in src/test/resources.

@wilkinsona wilkinsona closed this as not planned Won't fix, can't repro, duplicate, stale Oct 24, 2024
@wilkinsona wilkinsona added status: duplicate A duplicate of another issue and removed status: waiting-for-triage An issue we've not yet triaged labels Oct 24, 2024
@wyhasany
Copy link
Author

I don't think that's the case. Only one context is created due to the test context Framework's caching as each test class has identical configuration.

@wilkinsona you're correct. I was confused by seeing the Spring banner twice.

This is a duplicate of #38237. In addition to @nosan's suggestions, you can also avoid the problem by applying @DirtiesContext to DemoApplicationTests or by setting spring.test.context.cache.maxSize=1 in a spring.properties file in src/test/resources.

I would prefer to avoid recommending @DirtiesContext since it can significantly impact build times.

Would you mind if I submitted a PR to include information to docs about avoiding the use of @Container and @Testcontainers annotations with static fields in abstract base classes to prevent creating multiple contexts?

@wilkinsona
Copy link
Member

Thanks for the offer of a PR. I've added a note to #35236 as there are some other lifecycle issues to consider and I think it may be better to document them as a whole.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: duplicate A duplicate of another issue
Projects
None yet
Development

No branches or pull requests

4 participants