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.