36

I have a fairly simple Spring Boot application which exposes a small REST API and retrieves data from an instance of MongoDB. Queries to the MongoDB instance go through a Spring Data based repository. Some key bits of code below.

// Main application class
@EnableAutoConfiguration(exclude={MongoAutoConfiguration.class, MongoDataAutoConfiguration.class})
@ComponentScan
@Import(MongoConfig.class)
public class ProductApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProductApplication.class, args);
    }
}
// Product repository with Spring data
public interface ProductRepository extends MongoRepository<Product, String> {

    Page<Product> findAll(Pageable pageable);

    Optional<Product> findByLineNumber(String lineNumber);
}
// Configuration for "live" connections
@Configuration
public class MongoConfig {

    @Value("${product.mongo.host}")
    private String mongoHost;

    @Value("${product.mongo.port}")
    private String mongoPort;

    @Value("${product.mongo.database}")
    private String mongoDatabase;

    @Bean(name="mongoClient")
    public MongoClient mongoClient() throws IOException {
        return new MongoClient(mongoHost, Integer.parseInt(mongoPort));
    }

    @Autowired
    @Bean(name="mongoDbFactory")
    public MongoDbFactory mongoDbFactory(MongoClient mongoClient) {
        return new SimpleMongoDbFactory(mongoClient, mongoDatabase);
    }

    @Autowired
    @Bean(name="mongoTemplate")
    public MongoTemplate mongoTemplate(MongoClient mongoClient) {
        return new MongoTemplate(mongoClient, mongoDatabase);
    }
}
@Configuration
@EnableMongoRepositories
public class EmbeddedMongoConfig {

    private static final String DB_NAME = "integrationTest";
    private static final int DB_PORT = 12345;
    private static final String DB_HOST = "localhost";
    private static final String DB_COLLECTION = "products";

    private MongodExecutable mongodExecutable = null;

    @Bean(name="mongoClient")
    public MongoClient mongoClient() throws IOException {
        // Lots of calls here to de.flapdoodle.embed.mongo code base to 
        // create an embedded db and insert some JSON data
    }

    @Autowired
    @Bean(name="mongoDbFactory")
    public MongoDbFactory mongoDbFactory(MongoClient mongoClient) {
        return new SimpleMongoDbFactory(mongoClient, DB_NAME);
    }

    @Autowired
    @Bean(name="mongoTemplate")
    public MongoTemplate mongoTemplate(MongoClient mongoClient) {
        return new MongoTemplate(mongoClient, DB_NAME);
    }

    @PreDestroy
    public void shutdownEmbeddedMongoDB() {
        if (this.mongodExecutable != null) {
            this.mongodExecutable.stop();
        }
    }
}
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = TestProductApplication.class)
@IntegrationTest
@WebAppConfiguration
public class WtrProductApplicationTests {

    @Test
    public void contextLoads() {
        // Tests empty for now
    }

}
@EnableAutoConfiguration(exclude={MongoAutoConfiguration.class, MongoDataAutoConfiguration.class})
@ComponentScan
@Import(EmbeddedMongoConfig.class)
public class TestProductApplication {

    public static void main(String[] args) {
        SpringApplication.run(TestProductApplication.class, args);
    }
}

So the idea here is to have the integration tests (empty at the moment) connect to the embedded mongo instance and not the "live" one. However, it doesn't work. I can see the tests connecting to the "live" instance of Mongo, and if I shut that down the build simply fails as it is still attempting to connect to the live instance of Mongo. Does anyone know why this is? How do I get the tests to connect to the embedded instance?

Jon
  • 3,510
  • 6
  • 27
  • 32
  • 5
    Frankly I've been tinkering with Annotations and settings all day with no luck. Modifying a Spring Boot application to be testable seems to be akin to pulling teeth.. – Jon Jul 22 '15 at 16:04
  • These days you would most likely just need `spring.data.mongodb.port` and `spring.mongodb.embedded.*` as long as the maven dependency `de.flapdoodle.embed:de.flapdoodle.embed.mongo` is present. – aksh1618 Aug 21 '21 at 15:38

5 Answers5

41

Since Spring Boot version 1.3 there is an EmbeddedMongoAutoConfiguration class which comes out of the box. This means that you don't have to create a configuration file at all and if you want to change things you still can.

Auto-configuration for Embedded MongoDB has been added. A dependency on de.flapdoodle.embed:de.flapdoodle.embed.mongo is all that’s necessary to get started. Configuration, such as the version of Mongo to use, can be controlled via application.properties. Please see the documentation for further information. (Spring Boot Release Notes)

The most basic and important configuration that has to be added to the application.properties files is
spring.data.mongodb.port=0 (0 means that it will be selected randomly from the free ones)

for more details check: Spring Boot Docs MongoDb

magiccrafter
  • 5,175
  • 1
  • 56
  • 50
  • 3
    Thanks. The spring.data.mongodb.port=0 is important - without it, some tests fail randomly when many of them are run together. – Marwin Nov 08 '16 at 09:55
  • It still works. Nothing has changed for Spring Boot 1.5 in terms of Embedded Mongo Configuration for testing. just make sure you are adding the right version of flapdoodle.embed.mongo that corresponds to the version of the mongo driver – magiccrafter Apr 11 '17 at 16:15
  • Just adding 'de.flapdoodle.embed.mongo' dependecy, it's not work. Some one can provide e code example? – sintetico82 Oct 09 '17 at 09:51
  • 1
    You need both `de.flapdoodle.embed.mongo` **and** `com.mongodb` on your classpath: https://github.com/spring-projects/spring-boot/blob/master/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/mongo/embedded/EmbeddedMongoAutoConfiguration.java#L82 – Cisco Jun 02 '18 at 20:41
  • I had to add an empty class named MongoConfig with the @Configuration annotation in my tests to make the tests use the Embedded one. Without this, the tests pick up the MongoConfig from the main source. – mj3c Aug 21 '19 at 08:47
  • @magiccrafter cannot seem to find how to specify the mongo version in application.properties. Any ideas? – lorraine batol Sep 26 '19 at 09:49
  • 1
    found it: spring.mongodb.embedded.version . fulldoc https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html – lorraine batol Sep 26 '19 at 11:53
18

EDIT: see magiccrafter's answer for Spring Boot 1.3+, using EmbeddedMongoAutoConfiguration.

If you can't use it for any reason, keep reading.


Test class:

    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringApplicationConfiguration(classes = {
        Application.class, 
        TestMongoConfig.class // <--- Don't forget THIS
    })
    public class GameRepositoryTest {

        @Autowired
        private GameRepository gameRepository;

        @Test
        public void shouldCreateGame() {
            Game game = new Game(null, "Far Cry 3");
            Game gameCreated = gameRepository.save(game);
            assertEquals(gameCreated.getGameId(), gameCreated.getGameId());
            assertEquals(game.getName(), gameCreated.getName());
        }

    } 

Simple MongoDB repository:

public interface GameRepository extends MongoRepository<Game, String>     {

    Game findByName(String name);
}

MongoDB test configuration:

import com.mongodb.Mongo;
import com.mongodb.MongoClientOptions;
import de.flapdoodle.embed.mongo.MongodExecutable;
import de.flapdoodle.embed.mongo.MongodProcess;
import de.flapdoodle.embed.mongo.MongodStarter;
import de.flapdoodle.embed.mongo.config.IMongodConfig;
import de.flapdoodle.embed.mongo.config.MongodConfigBuilder;
import de.flapdoodle.embed.mongo.config.Net;
import de.flapdoodle.embed.mongo.distribution.Version;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.mongo.MongoProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;

@Configuration
public class TestMongoConfig {

    @Autowired
    private MongoProperties properties;

    @Autowired(required = false)
    private MongoClientOptions options;

    @Bean(destroyMethod = "close")
    public Mongo mongo(MongodProcess mongodProcess) throws IOException {
        Net net = mongodProcess.getConfig().net();
        properties.setHost(net.getServerAddress().getHostName());
        properties.setPort(net.getPort());
        return properties.createMongoClient(this.options);
    }

    @Bean(destroyMethod = "stop")
    public MongodProcess mongodProcess(MongodExecutable mongodExecutable) throws IOException {
        return mongodExecutable.start();
    }

    @Bean(destroyMethod = "stop")
    public MongodExecutable mongodExecutable(MongodStarter mongodStarter, IMongodConfig iMongodConfig) throws IOException {
        return mongodStarter.prepare(iMongodConfig);
    }

    @Bean
    public IMongodConfig mongodConfig() throws IOException {
        return new MongodConfigBuilder().version(Version.Main.PRODUCTION).build();
    }

    @Bean
    public MongodStarter mongodStarter() {
        return MongodStarter.getDefaultInstance();
    }

}

pom.xml

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>
        <dependency>
            <groupId>de.flapdoodle.embed</groupId>
            <artifactId>de.flapdoodle.embed.mongo</artifactId>
            <version>1.48.0</version>
            <scope>test</scope>
        </dependency>
cahen
  • 15,807
  • 13
  • 47
  • 78
  • 1
    Turns out the bit I was missing was the latter part of: @SpringApplicationConfiguration(classes = {Application.class, TestMongoConfig.class}) - I didn't realise you needed the config in the annotation too in order to have it picked up – Jon Jul 22 '15 at 16:50
  • 4
    I followed your implementation but its not working for me when i specify in the POM that scope is test, when building the app i get the error "Caused by: java.lang.ClassNotFoundException: de.flapdoodle.embed.mongo.distribution.IFeatureAwareVersion" and when i removed the scope test from the POM i get the following error "NoSuchBeanDefinitionException: No bean named 'embeddedMongoServer' is defined" – naoru Dec 31 '15 at 19:42
  • I'm getting Error creating bean with name 'testMongoConfig', No qualifying bean error. – Francis Zabala May 01 '16 at 08:46
  • 1
    Repository gets the actual collection config instead of getting the config from TestMongoConfig class. can anyone help me to solve this issue – devanathan Aug 11 '16 at 07:14
8

In version 1.5.7 use just this:

@RunWith(SpringRunner.class)
@DataMongoTest
public class UserRepositoryTests {

    @Autowired
    UserRepository repository;

    @Before
    public void setUp() {

        User user = new User();

        user.setName("test");
        repository.save(user);
    }

    @Test
    public void findByName() {
        List<User> result = repository.findByName("test");
        assertThat(result).hasSize(1).extracting("name").contains("test");
    }

}

And

        <dependency>
            <groupId>de.flapdoodle.embed</groupId>
            <artifactId>de.flapdoodle.embed.mongo</artifactId>
        </dependency>
Nataniel Paiva
  • 485
  • 4
  • 9
5

I'll complete previous answer

pom.xml

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.3.2.RELEASE</version>
</parent>
 ...
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-mongodb</artifactId>
    </dependency>
    <dependency>
        <groupId>de.flapdoodle.embed</groupId>
        <artifactId>de.flapdoodle.embed.mongo</artifactId>
        <version>${embedded-mongo.version}</version>
    </dependency>

MongoConfig

@Configuration
@EnableAutoConfiguration(exclude = { EmbeddedMongoAutoConfiguration.class })
public class MongoConfig{
}
panser
  • 1,949
  • 22
  • 16
  • This is what I needed in my Mongo config to get it to stop killing my tests for Swagger2 (the subsequent tests would not start up correctly) The above items got me thinking, this ended up being it for me. Spring version 4.x Spring autoconfigure 1.3.2 `@EnableAutoConfiguration(exclude={EmbeddedMongoAutoConfiguration.class` – ken Sep 26 '16 at 11:24
3

Make sure you are explicit with your @ComponentScan. By default,

If specific packages are not defined scanning will occur from the package of the class with this annotation. (@ComponentScan Javadoc)

Therefore, if your TestProductApplication and ProductApplication configurations are both in the same package, it is possible Spring is component-scanning your ProductApplication configuration and using that.

Additionally, I would recommend putting your Test mongo beans into a 'test' or 'local' profile and using the @ActiveProfiles annotation in your test class to enable the test/local profile.

FGreg
  • 14,110
  • 10
  • 68
  • 110