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.
Comment From: nosan
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"));
}
}
Comment From: wilkinsona
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 https://github.com/spring-projects/spring-boot/issues/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.
Comment From: wyhasany
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 https://github.com/spring-projects/spring-boot/issues/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?
Comment From: wilkinsona
Thanks for the offer of a PR. I've added a note to https://github.com/spring-projects/spring-boot/issues/35236 as there are some other lifecycle issues to consider and I think it may be better to document them as a whole.