Use Spring Data random (embedded) Mongo port with NoSQL JUnit @Rule

天涯浪子 提交于 2021-01-28 04:15:59

问题


I'm currently trying to write an Integration test class which uses Spring Data Mongo repositories. I use an embedded Mongo instance provided by the de.flapdoodle.embed.mongo dependency. Spring Data documentation specifies that we only have to put this dependency in the project and the EmbedMongoAutoConfiguration takes care of the rest.

For now, that's ok, and setting the port to 0 makes the auto configuration process to find a free port to launch the mongo instance on.

This feature is necessary for me to avoid collision with other tests (which are run on a Jenkins CI server along with other project of my company).

The problem comes now, I want to be able to inject some test data from some external file before each of my test method run. I found out that NoSQL Unit can do this with a simple method annotation and a JUnit @Rule.

Here is an example:

@Value("${local.mongo.port}")
private int mongoPort; // <- still 0 a the time the Rule below is created.

@Rule
public MongoDbRule managedMongoDb = new MongoDbRule(MongoDbConfigurationBuilder.mongoDb().databaseName("myAwesomeDb").port(mongoPort).build());

@Test
@UsingDataSet(locations = "testdata.json", loadStrategy = LoadStrategyEnum.CLEAN_INSERT)
public void testMyData() {
   // ...
}

My problem is that, the @Rule needs the Mongo port in its builder to instantiate the underlying MongoClient, but at the time the @Rule is instantiated, the Spring context is not fully initialized and the EmbeddedMongoAutoConfiguration has not published the port yet.

So my question is, is there anyone who has ever used the Embedded Mongo feature with NoSQL Unit, and is there any way to, for example create the @Rule after the Spring context is initialized ?

I was wondering if finding the free port myself (in a static way), setting it to the @Rule and then tell the EmbeddedMongoAutoConfiguration to use it by overriding the IMongodConfig bean was a good idea ? or is there a "simpler" way ?

Note: I just saw that flapdoodle library provides a class and a static method to find a free server port and its used by Spring like this:

Network.getFreeServerPort(getHost()), Network.localhostIsIPv6()))

Thanks everyone in advance!

EDIT: I tried the solution I talked just above, and it seems to work, though I still think it's a bit "verbose" and dirty.

private static final Logger log = LoggerFactory.getLogger(MyAwesomeIT.class);
private static int mongoPort;
static {
    try {
        mongoPort = Network.getFreeServerPort();
    } catch (IOException e) {
        log.error("Error while trying to find a free port for Mongo", e);
        mongoPort = -1; // test should then not work
    }
}

@Rule
public MongoDbRule managedMongoDb = new MongoDbRule(MongoDbConfigurationBuilder.mongoDb().databaseName("myAwesomeDb").port(mongoPort).build());

then in the associated configuration class :

@Configuration
@EnableAutoConfiguration
@EnableMongoRepositories
@EnableConfigurationProperties(MongoProperties.class)
static class ContextConfiguration {
    @Autowired
    private MongoProperties mongoProperties;

    @PostConstruct
    public void init() {
        // Here, I override the port property
        mongoProperties.setPort(mongoPort);
    }
}

回答1:


Refining the solution given by @user6599111, it is possible to obtain the port randomly chosen by Flapdoodle Embedded Mongo, simply injecting an object of type IMongodConfig.

Spring Boot builds automagically this object for you, as stated here.

Then, the configuration class will become the following.

@Configuration
@EnableAutoConfiguration(exclude = { EmbeddedMongoAutoConfiguration.class })
public class MongoConfiguration {
    @Autowired
    private Environment environment;

    @Autowired
    private MongoProperties properties;

    @Autowired(required = false)
    private MongoClientOptions options;

    @Autowired
    private IMongodConfig mongoConfig;

    @Bean
    public MongoClient mongo() throws Exception {
        properties.setPort(mongoConfig.net().getPort());
        return properties.createMongoClient(this.options, this.environment);
    }
}



回答2:


I had the same problem and this was my solution


@Configuration
public class EmbeddedMongoConfig extends AbstractMongoConfiguration {

    @Autowired
    private Environment environment;

    @Autowired
    private MongoProperties properties;

    @Autowired(required = false)
    private MongoClientOptions options;

    @Override
    protected String getDatabaseName() {
        return properties.getDatabase();
    }

    @Override
    @Bean(destroyMethod = "close")
    public Mongo mongo() throws Exception {
        properties.setPort(Network.getFreeServerPort());
        return properties.createMongoClient(this.options, this.environment);
    }

}


@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = { AppRunner.class, EmbeddedMongoConfig.class })
public class BaseTest {

}


public class CategoryServiceTest extends BaseTest{

    @Autowired
    private CategoryService categoryService;

    @Test
    public void someTest(){
        fail("Test not implemented");
    }

}




回答3:


I have tried this:

int mongoPort = SocketUtils.findAvailableTcpPort();

Source: https://docs.spring.io/spring/docs/4.0.5.RELEASE/javadoc-api/org/springframework/util/SocketUtils.html

This worked for me.

We had embedded Mongo running for unit tests and where there were multiple applications building on Jenkins, some of them failed since the port was same for everyone. Manually changing the ports was also tried but since there were many applications and some of them used a common base class, it was failing.

I am not sure about the @Rule part but you can try this.



来源:https://stackoverflow.com/questions/36176132/use-spring-data-random-embedded-mongo-port-with-nosql-junit-rule

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!