在运行集成测试时嵌入MongoDB

135

我的问题是 这个问题 的变体。

由于我的Java Web应用程序项目需要大量的读取过滤器/查询以及与GridFS等工具进行交互,我正在努力思考如何合理地使用MongoDB来实现上述解决方案。

因此,我考虑在集成测试期间运行MongoDB的嵌入式实例。我希望它可以自动启动(对于每个测试或整个套件),为每个测试清除数据库并在结束时关闭。这些测试可能在开发机器以及CI服务器上运行,因此我的解决方案还需要可移植性

有没有更多了解MongoDB的人可以帮助我了解这种方法的可行性,并/或者提供任何可能帮助我入门的阅读材料?

我也愿意听取其他人对我如何解决这个问题的建议...


如果您正在使用Maven,您可以使用我们的http://mvnrepository.com/artifact/com.wenzani/mongodb-maven-plugin。 - markdsievers
你也可以查看这个项目,它模拟了一个运行在JVM内存中的MongoDB。 https://github.com/thiloplanz/jmockmongo 但是它仍然在开发中。 - Sebastien Lorber
不仅仅是为了单元测试,如果你正在使用Linux并且想要将MongoDB(甚至是集群)作为内存部署运行,那么请阅读这篇博客文章。http://edgystuff.tumblr.com/post/49304254688 虽然像RavenDB一样开箱即用会更好。 - Tamir
类似于此处提到的embedmongo-maven-plugin,也有一个Gradle Mongo Plugin可用。与Maven插件一样,它还包装了flapdoodle EmbeddedMongoDb api,并允许您从Gradle构建中运行Mongo的托管实例。 - Robert Taylor
在这里检查代码示例:https://github.com/familysyan/embedded-mongo-integ。无需安装,也无需依赖。这只是一个平台无关的ant脚本,为您下载和设置。它还会在测试后清理一切。 - Edmond
11个回答

103

我发现了一个很有前途并且可以实现你所要求的功能的嵌入式MongoDB库。

当前支持MongoDB版本:1.6.53.1.6,只要从配置的镜像下载到二进制文件即可。

这里是一个简短的示例,我刚刚尝试过,它完美地工作:

public class EmbeddedMongoTest {
    private static final String DATABASE_NAME = "embedded";

    private MongodExecutable mongodExe;
    private MongodProcess mongod;
    private Mongo mongo;

    @Before
    public void beforeEach() throws Exception {
        MongoDBRuntime runtime = MongoDBRuntime.getDefaultInstance();
        mongodExe = runtime.prepare(new MongodConfig(Version.V2_3_0, 12345, Network.localhostIsIPv6()));
        mongod = mongodExe.start();
        mongo = new Mongo("localhost", 12345);
    }

    @After
    public void afterEach() throws Exception {
        if (this.mongod != null) {
            this.mongod.stop();
            this.mongodExe.stop();
        }
    }

    @Test
    public void shouldCreateNewObjectInEmbeddedMongoDb() {
        // given
        DB db = mongo.getDB(DATABASE_NAME);
        DBCollection col = db.createCollection("testCollection", new BasicDBObject());

        // when
        col.save(new BasicDBObject("testDoc", new Date()));

        // then
        assertThat(col.getCount(), Matchers.is(1L));
    }
}

1
刚刚在 Mac 上使用了这个库,对 Mongo API 进行了 JUnit 测试,完美运行。强烈推荐。 - Martin Dow
1
+1 太棒了!一年前我开始使用mongodb时,没有编程方式来测试数据库是其中的缺点之一。我们通过在每个环境上配置一个测试实例来解决这个问题,该实例通过Java属性文件进行配置,但当然需要在每个环境中安装mongo。这看起来将解决所有这些问题。 - andyb
不错!由于它不再准确,我删除了我的答案。有人知道这个有多成熟吗?我可以想象在非常低的层次上模拟MongoDB可能会相当复杂,并且从源代码来看,它看起来相当高级。 - Remon van Vliet
最终在我的项目中使用了这个,可以报告说它非常容易设置和运行。低级调用都是官方 com.mongodb Java API 的一部分,因此它并不比使用常规API更复杂。 - andyb
24
注意这个解决方案。它只是收集有关当前操作系统的信息,并从互联网上下载适用于特定平台的MongoDB二进制文件,运行守护程序并进行其他配置。作为企业解决方案,这不太可靠。模拟可能是唯一真正的选择。 - James Watkins

25

这是一个更新后的(适用于2022年)来自@rozky的被采纳答案版本(Mongo和Embedded MongoDB库都发生了很多变化)。

package com.example.mongo;

import com.mongodb.BasicDBObject;
import com.mongodb.MongoClient;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import de.flapdoodle.embed.mongo.MongodExecutable;
import de.flapdoodle.embed.mongo.MongodProcess;
import de.flapdoodle.embed.mongo.MongodStarter;
import de.flapdoodle.embed.mongo.config.MongodConfig;
import de.flapdoodle.embed.mongo.config.Net;
import de.flapdoodle.embed.mongo.distribution.Version;
import de.flapdoodle.embed.process.runtime.Network;
import java.util.Date;
import org.junit.After;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;

public class EmbeddedMongoTest {

    private static final String DATABASE_NAME = "embedded";

    private MongodExecutable mongodExe;
    private MongodProcess mongod;
    private MongoClient mongo;

    @Before
    public void beforeEach() throws Exception {
        MongodStarter starter = MongodStarter.getDefaultInstance();
        String bindIp = "localhost";
        int port = 12345;
        MongodConfig mongodConfig = MongodConfig.builder()
            .version(Version.Main.PRODUCTION)
            .net(new Net(bindIp, port, Network.localhostIsIPv6()))
            .build();
        this.mongodExe = starter.prepare(mongodConfig);
        this.mongod = mongodExe.start();
        this.mongo = new MongoClient(bindIp, port);
    }

    @After
    public void afterEach() throws Exception {
        if (this.mongod != null) {
            this.mongod.stop();
            this.mongodExe.stop();
        }
    }

    @Test
    public void shouldCreateNewObjectInEmbeddedMongoDb() {
        // given
        MongoDatabase db = mongo.getDatabase(DATABASE_NAME);
        db.createCollection("testCollection");
        MongoCollection<BasicDBObject> col = db.getCollection("testCollection", BasicDBObject.class);

        // when
        col.insertOne(new BasicDBObject("testDoc", new Date()));

        // then
        assertEquals(1L, col.countDocuments());
    }

}

1
在每个测试中重复启动和停止嵌入式Mongo会导致大多数测试失败。最好在所有测试之前启动并在所有测试执行完毕后关闭。 - DBS
没错,这个代码完美运行。我是从baeldung来的 - 这个例子一直报错Caused by: java.lang.IllegalArgumentException: Database name must not be empty!虽然我已经提供了它(MongoClients.create(String.format(CONNECTION_STRING, ip, port)), "cipresale"); - kristjan reinhold
这两个导入语句需要帮忙。即使添加了 <artifactId>de.flapdoodle.embed.mongo</artifactId>,仍然会出现错误。 请导入以下两个库: import de.flapdoodle.embed.mongo.config.IMongodConfig; import de.flapdoodle.embed.mongo.config.MongodConfigBuilder; - sashikanta
1
@sashikanta,我刚刚更新了代码以适应最新的更改(具体来说,我将IMongodConfig mongodConfig = new MongodConfigBuilder()更改为MongodConfig mongodConfig = MongodConfig.builder())。请现在再试一次。 - Collin Krawll
@CollinKrawll 现在完美地工作了。非常感谢你。 - sashikanta
显示剩余2条评论

18

有一个Foursquare产品Fongo。 Fongo是Mongo的内存Java实现。它拦截对标准mongo-java-driver的查找、更新、插入、删除和其他方法的调用。主要用于轻量级单元测试,您不必启动Mongo进程。


1
Fongo是否会拦截对网络的调用,例如对localhost:27017的调用,以便作为即插即用的虚假服务器,从而实现集成测试而无需进行代码更改? - user2066936
mongo-java-server 是一个即插即用的虚拟服务器实现,可用于集成测试而无需进行代码更改。 - Benedikt Waldvogel

8
如果您正在使用Maven,您可能会对我创建的插件感兴趣,该插件包装了flapdoodle.de 'embedded mongo' API

embedmongo-maven-plugin

它提供了一个start目标,您可以使用它来启动任何版本的MongoDB(例如在pre-integration-test期间),以及一个stop目标,该目标将停止MongoDB(例如在post-integration-test期间)。
使用此插件的真正好处是,不需要预先安装MongoDB。 MongoDB二进制文件将下载并存储在~/.embedmongo中,以备将来构建使用。

这是 Leiningen 的 Clojure 版本:https://github.com/joelittlejohn/lein-embongo - joelittlejohn

7

5
使用spring-boot 1.3,您可以使用EmbeddedMongoAutoConfiguration。 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{
}

1
你能解释一下"@EnableAutoConfiguration(exclude = { EmbeddedMongoAutoConfiguration.class })"注解实际上是在做什么吗? - Bruno Negrão Zica
原因很可能是 de.flapdoodle.embed.mongo 依赖项未标记为测试范围。如果不排除它并在生产应用程序设置中运行嵌入式Mongo,则无法选择它。 - Sergey Shcherbakov

4
自MongoDB 3.2.6版本起,您可以在内存中运行MongoDB。根据官方网站的介绍:
自MongoDB企业版3.2.6版本开始,64位版本支持内存存储引擎。除了某些元数据和诊断数据之外,内存存储引擎不维护任何磁盘上的数据,包括配置数据、索引、用户凭据等。

2

不仅适用于单元测试,还解释了如何在REST API中使用内存MongoDB。

Maven依赖项:

        <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>
        </dependency>

=============================================================================

application.properties

server.port = 8080
spring.data.mongodb.database=user_db
spring.data.mongodb.port=27017
spring.data.mongodb.host=localhost

=============================================================================

UserRepository.java

public interface UserRepository extends MongoRepository{

}

参考链接和所有Java代码请使用以下链接:(逐步解释)

https://www.youtube.com/watch?v=2Tq2Q7EzhSA&t=7s


0

为了运行嵌入式Mongodb进行集成测试,需要以下Maven依赖项:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
            <version>2.5.2</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
            <version>2.5.2</version>
        </dependency>

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

尝试使用以下代码片段来处理EmbeddedMongoAutoConfiguration

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.mongo.MongoProperties;
import org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class EmbeddedMongoApplication {

    public static void main(String[] args) {
         System.setProperty("os.arch", "x86_64");
         SpringApplication.run(EmbeddedMongoApplication.class, args);
    }
    
    @Bean
    public EmbeddedMongoAutoConfiguration embeddedMongoAutoConfiguration(MongoProperties mongoProperties) {
        return new EmbeddedMongoAutoConfiguration(mongoProperties);
    }
}

注意:

嵌入式mongodb将被下载到以下路径。因此,请考虑该路径是否具有适当的权限。

Linux : $HOME/.embedmongo/linux/mongodb-linux-x86_64-3.2.2.tgz
Windows : C:\Users\<username>\.embedmongo\win32\mongodb-win32-x86_64-3.x.x.zip

0

使用storageEngine='ephemeralForTest'参数执行mongod时,性能更佳。

new MongodConfigBuilder()
    .version(Version.Main.PRODUCTION)
    .cmdOptions(new MongoCmdOptionsBuilder()
         .useStorageEngine("ephemeralForTest")
         .build())
    .net(new Net("localhost", port, Network.localhostIsIPv6()))
    .build()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接