Search code examples
mongodbspring-boottestcontainers

Is there a way to abstract MongoDbContainer with TestContainers in a Spring Boot App?


I'm attempting to use the latest best practices for Spring Boot 3.x and TestContainers to integration test a MongoDB Repository. From a post on the Spring.io blog:

Under the covers, @ServiceConnection discovers the type of container that is annotated and creates a ConnectionDetails bean for it. This replaces the need for the @DynamicPropertySource code, so you can just remove it.

Because I have several MongoDb integration tests, I setup an Abstract test class that creates a static MongoDb container under the hood:

@SpringBootTest
@Testcontainers(disabledWithoutDocker = true)
public abstract class MongoContainerTest {

    @Container
    @ServiceConnection
    static MongoDBContainer mongoDBContainer = new MongoDBContainer("mongo:7.0.9-rc1");
}

and then my test class extends it:

class RoleRepositoryTest extends MongoContainerTest {
    @Autowired
    RoleRepository roleRepository;

    private Role expectedRole;

    @BeforeEach
    void setUp() {
        roleRepository.deleteAll();
    }
    @Test
    void testFind() {...}

}

Run individually, these tests work just fine. However, when I attempt to run them all as part of a test suite from maven or Intellij, only the first MongoContainerTest child works, while the rest give an error:

INFO  org.mongodb.driver.cluster Exception in monitor thread while connecting to server localhost:55114
com.mongodb.MongoSocketOpenException: Exception opening socket
    at com.mongodb.internal.connection.SocketStream.lambda$open$0(SocketStream.java:84)
    at java.base/java.util.Optional.orElseThrow(Optional.java:403)
    at com.mongodb.internal.connection.SocketStream.open(SocketStream.java:84)
    at com.mongodb.internal.connection.InternalStreamConnection.open(InternalStreamConnection.java:211)
    at com.mongodb.internal.connection.DefaultServerMonitor$ServerMonitorRunnable.lookupServerDescription(DefaultServerMonitor.java:196)
    at com.mongodb.internal.connection.DefaultServerMonitor$ServerMonitorRunnable.run(DefaultServerMonitor.java:156)
    at java.base/java.lang.Thread.run(Thread.java:1583)
Caused by: java.net.ConnectException: Connection refused
    at java.base/sun.nio.ch.Net.pollConnect(Native Method)
    at java.base/sun.nio.ch.Net.pollConnectNow(Net.java:682)

I tried different things to get this configuration working. Such as setting the .withReuse(true) flag on. But I keep getting the same issue. I am guessing that after the first test runs the docker container shuts down and the next test is unable to open one. Any ideas here?


Solution

  • My takeaway is, for persisting the container across tests in the manner I am describing, use of @ServiceConnection is a no-go. My abstract test was modified to:

    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.test.context.DynamicPropertyRegistry;
    import org.springframework.test.context.DynamicPropertySource;
    import org.testcontainers.containers.MongoDBContainer;
    import org.testcontainers.junit.jupiter.Testcontainers;
    
    /**
     * Base class for tests that require a running MongoDB container.
     * Simply extend to inherit a spring-managed mongo repository
     */
    @SpringBootTest
    @Testcontainers
    public abstract class MongoContainerTest {
    
        protected static final MongoDBContainer mongoDBContainer;
        static {
            mongoDBContainer = new MongoDBContainer("mongo:<your_version_here>");
            mongoDBContainer.start();
        }
    
        @DynamicPropertySource
        static void setProperties(DynamicPropertyRegistry registry) {
            registry.add("spring.data.mongodb.uri", mongoDBContainer::getReplicaSetUrl);
        }
    
    }
    

    At that point, I can create a mongo integration test by simple extending this class and Autowiring the repository-under-test into my unit test class.