javamongodbspring-bootjunit5

How to use embedded MongoDB with SpringBoot v2.6.2?


I'm using Spring Boot v2.6.2 and Java v17 and trying to test my MongoConnection without having a MongoDBService running because it should be tested with embedded in-memory MongoDB on the build machine, no need to set up an extra MongoDB service there. For sure on the productive system, it should use a full MongoDB.

I try to get this easy example running:

@DataMongoTest
public class MongoTest
{
    @Autowired
    private UserRepository userRepository;

    @AfterEach
    void cleanUpDatabase()
    {
        this.userRepository.deleteAll();
    }

    @Test
    void bootstrapTestDataWithMongoTemplate() {
        final var restaurant = new User( "123", "ABC", "DEF" );
        this.userRepository.insert( restaurant );

        final var found = this.userRepository.findByFirstName( "ABC" );

        System.out.println( found );
    }
}

UserRepository is an interface that extends MongoRepository. User itself is just a DBEntity with @Document annotation. If I let the SpringBoot application run, not in test mode, everything works fine, because MongoDB is running at the specified location. But for the test, I want to let it run as an in-memory DB.

But Springboot wants to connect for the test.

    2022-01-20 08:31:57.489  INFO 3976 --- [           main] org.mongodb.driver.cluster               : Cluster created with settings {hosts=[localhost:27017], mode=SINGLE, requiredClusterType=UNKNOWN, serverSelectionTimeout='30000 ms'}
2022-01-20 08:31:57.521  INFO 3976 --- [localhost:27017] org.mongodb.driver.cluster               : Exception in monitor thread while connecting to server localhost:27017

com.mongodb.MongoSocketOpenException: Exception opening socket
    at com.mongodb.internal.connection.SocketStream.open(SocketStream.java:70) ~[mongodb-driver-core-4.4.0.jar:na]
    at com.mongodb.internal.connection.InternalStreamConnection.open(InternalStreamConnection.java:180) ~[mongodb-driver-core-4.4.0.jar:na]
    at com.mongodb.internal.connection.DefaultServerMonitor$ServerMonitorRunnable.lookupServerDescription(DefaultServerMonitor.java:188) ~[mongodb-driver-core-4.4.0.jar:na]
    at com.mongodb.internal.connection.DefaultServerMonitor$ServerMonitorRunnable.run(DefaultServerMonitor.java:152) ~[mongodb-driver-core-4.4.0.jar:na]
    at java.base/java.lang.Thread.run(Thread.java:833) ~[na:na]
Caused by: java.net.ConnectException: Connection refused: no further information
    at java.base/sun.nio.ch.Net.pollConnect(Native Method) ~[na:na]
    at java.base/sun.nio.ch.Net.pollConnectNow(Net.java:672) ~[na:na]
    at java.base/sun.nio.ch.NioSocketImpl.timedFinishConnect(NioSocketImpl.java:542) ~[na:na]
    at java.base/sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:597) ~[na:na]
    at java.base/java.net.SocksSocketImpl.connect(SocksSocketImpl.java:327) ~[na:na]
    at java.base/java.net.Socket.connect(Socket.java:633) ~[na:na]
    at com.mongodb.internal.connection.SocketStreamHelper.initialize(SocketStreamHelper.java:107) ~[mongodb-driver-core-4.4.0.jar:na]
    at com.mongodb.internal.connection.SocketStream.initializeSocket(SocketStream.java:79) ~[mongodb-driver-core-4.4.0.jar:na]
    at com.mongodb.internal.connection.SocketStream.open(SocketStream.java:65) ~[mongodb-driver-core-4.4.0.jar:na]
    ... 4 common frames omitted

Any suggestions on how to do this? Is @DataMongoTest testing in the wrong way? I thought the integrated flapdoodle dependency would inject it automatically in the test case.

The pom itself is also not that complex:

<properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>3.0.0</version>
        </dependency>
        <dependency>
            <groupId>com.github.openjson</groupId>
            <artifactId>openjson</artifactId>
            <version>1.0.11</version>
        </dependency>
        <dependency>
            <groupId>com.github.erosb</groupId>
            <artifactId>everit-json-schema</artifactId>
            <version>1.14.0</version>
        </dependency>
    </dependencies>

Solution

  • The documentation states:

    2.2.4. Embedded Mongo

    Spring Boot offers auto-configuration for Embedded Mongo. To use it in your Spring Boot application, add a dependency on de.flapdoodle.embed:de.flapdoodle.embed.mongo and set the spring.mongodb.embedded.version property to match the version of MongoDB that your application will use in production. The default download configuration allows access to most of the versions listed in Embedded Mongo’s Version class as well as some others. Configuring an inaccessible version will result in an error when attempting to download the server. Such an error can be corrected by defining an appropriately configured DownloadConfigBuilderCustomizer bean.

    The port that Mongo listens on can be configured by setting the spring.data.mongodb.port property. To use a randomly allocated free port, use a value of 0. The MongoClient created by MongoAutoConfiguration is automatically configured to use the randomly allocated port. If you do not configure a custom port, the embedded support uses a random port (rather than 27017) by default.

    If you have SLF4J on the classpath, the output produced by Mongo is automatically routed to a logger named org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongo.

    You can declare your own IMongodConfig and IRuntimeConfig beans to take control of the Mongo instance’s configuration and logging routing. The download configuration can be customized by declaring a DownloadConfigBuilderCustomizer bean.

    Therefore add the below dependency in scope:test if you want it to only be applied for Tests.

        <dependency>
            <groupId>de.flapdoodle.embed</groupId>
            <artifactId>de.flapdoodle.embed.mongo</artifactId>
            <scope>test</scope>
        </dependency>
    

    As well you need to set the version in your application.properties file:

    spring.mongodb.embedded.version=4.0.21
    

    https://docs.spring.io/spring-boot/docs/current/reference/html/data.html#data.nosql.mongodb.embedded


    Working Example:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.6.2</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.example.mongodb.embedded</groupId>
        <artifactId>mongodb-app</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>mongodb-app</name>
        <description>Demo project for usage of embedded mongodb</description>
        <properties>
            <java.version>11</java.version>
        </properties>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-mongodb</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.16</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>de.flapdoodle.embed</groupId>
                <artifactId>de.flapdoodle.embed.mongo</artifactId>
                <scope>test</scope>
            </dependency>
    
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <executions>
                        <execution>
                            <id>integration-test</id>
                            <goals>
                                <goal>test</goal>
                            </goals>
                            <phase>integration-test</phase>
                            <configuration>
                                <excludes>
                                    <exclude>none</exclude>
                                </excludes>
                                <includes>
                                    <include>**/*IT.java</include>
                                </includes>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    
    </project>
    

    UserRepository:

    public interface UserRepository extends MongoRepository<User, Long> {
    
        //Spring converts this to Regex findByFirstnameRegex(String firstname)  {"firstname" : {"$regex" : firstname }}
        // automatically
        public List<User> findByFirstName(String firstName);
    
    }
    

    User:

    @Data
    @Builder
    public class User {
    
        @Id
        private Long id;
    
        private String firstName;
        private String lastName;
    
    }
    

    Test:

    @DataMongoTest
    @TestInstance(TestInstance.Lifecycle.PER_CLASS)
    public class UserControllerIT {
    
        @Autowired
        private UserRepository userRepository;
    
        @BeforeAll
        public void setup(){
    
            userRepository.save(User.builder().id(1L).firstName("James").lastName("Bond").build());
            userRepository.save(User.builder().id(2L).firstName("James").lastName("Farley").build());
            userRepository.save(User.builder().id(3L).firstName("Marley").lastName("Hemp").build());
            userRepository.save(User.builder().id(4L).firstName("James").lastName("Bond").build());
    
        }
    
        @Test
        public void test_getById_successfull() throws Exception {
            Assertions.assertEquals("James", userRepository.findByFirstName("James").get(0).getFirstName());
        }
    
    }
    

    src/test/resources/application.properties

    spring.data.mongodb.database=test
    spring.data.mongodb.port=27017
    spring.mongodb.embedded.version=4.0.2