运行集成测试时嵌入 MongoDB

Posted

技术标签:

【中文标题】运行集成测试时嵌入 MongoDB【英文标题】:Embedded MongoDB when running integration tests 【发布时间】:2011-09-20 04:51:00 【问题描述】:

我的问题是this one 的变体。

由于我的 Java Web 应用程序项目需要大量读取过滤器/查询以及与 GridFS 等工具的接口,因此我正在努力想出一种以上述解决方案建议的方式使用 MongoDB 的明智方法。

因此,我正在考虑在集成测试的同时运行一个嵌入式 MongoDB 实例。我希望它自动启动(对于每个测试或整个套件),刷新数据库对于每个测试,然后关闭 最后。这些测试可能会在开发机器和 CI 服务器上运行,所以我的解决方案也需要便携

任何对 MongoDB 有更多了解的人都可以帮助我了解这种方法的可行性,和/或建议任何可以帮助我入门的阅读材料吗?

我也愿意接受人们可能提出的关于如何解决这个问题的其他建议...

【问题讨论】:

如果你使用的是maven,你可以使用我们的mvnrepository.com/artifact/com.wenzani/mongodb-maven-plugin 你也可以查看这个项目,它在 JVM 内存中模拟了一个 MongoDB。 github.com/thiloplanz/jmockmongo 但它仍在开发中。 不 [仅用于] 单元测试,但如果您使用 Linux 时希望将 MongoDB(甚至是集群)作为内存部署运行,请阅读这篇博文。 edgystuff.tumblr.com/post/49304254688 不过,如果能像 RavenDB 一样开箱即用,那就太好了。 与这里提到的 embedmongo-maven-plugin 类似,也有一个Gradle Mongo Plugin 可用。与 Maven 插件一样,它也包装了 flapdoodle EmbeddedMongoDb api,并允许您从 Gradle 构建中运行 Mongo 的托管实例。 在此处查看此代码示例:github.com/familysyan/embedded-mongo-integ。无需安装,无需依赖。它只是一个独立于平台的 ant 脚本,可以为您下载和设置。它还会在您的测试后清理所有内容。 【参考方案1】:

我找到了Embedded 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));
    

【讨论】:

刚刚使用了这个库,它在 Mac 上完美地运行了 JUnit 测试 Mongo API。推荐。 +1 很棒的发现!一年前我第一次开始使用 mongodb 时,没有针对数据库进行测试的编程方式是缺点之一。我们通过在每个环境中都有一个测试实例来解决这个问题,通过 Java 属性文件进行配置,但当然需要在每个环境中安装 mongo。这看起来会解决所有问题。 不错!删除了我的答案,因为它不再准确。有人知道这有多成熟吗?我可以想象它必须在非常低的级别上模拟 MongoDB 会非常复杂,并且从源代码来看它看起来非常高级。 终于在我的项目中使用了这个,并且可以报告它非常容易设置和运行。底层调用都是官方com.mongodbJava API的一部分,所以并不比使用常规API复杂。 小心这个解决方案。它只是收集有关当前操作系统的信息并从 Internet 下载适当的特定于平台的 MongoDB 二进制文件,运行守护程序并执行一些其他配置工作。作为企业解决方案,事实并非如此。模拟可能是唯一真正的选择。【参考方案2】:

如果您使用的是 Maven,您可能会对我创建的包含 flapdoodle.de 'embedded mongo' API 的插件感兴趣:

embedmongo-maven-plugin

它提供了一个start 目标,您可以使用它来启动您想要的任何版本的MongoDB(例如在pre-integration-test 期间),以及一个将停止MongoDB 的stop 目标(例如在post-integration-test 期间)。

与其他插件相比,使用此插件的真正好处是不需要事先安装 MongoDB。下载 MongoDB 二进制文件并将其存储在 ~/.embedmongo 以供将来构建。

【讨论】:

这里是 Leiningen 的 Clojure 版本:github.com/joelittlejohn/lein-embongo【参考方案3】:

如果您使用的是 sbt 和 specs2,我为 embedmongo 编写了相同类型的包装器

https://github.com/athieriot/specs2-embedmongo

【讨论】:

【参考方案4】:

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

【讨论】:

Fongo 是否会拦截对网络的调用,例如到 localhost:27017 以便它可以充当插入式假服务器以启用集成测试而无需更改代码? mongo-java-server 是一个插入式假服务器实现,无需更改代码即可用于集成测试。【参考方案5】:

在生产中,您将使用真实的数据库。

如果您希望您的测试反映您的产品在生产中的行为方式,请使用 Mongo 的真实实例。

伪造的实现可能与真实的不完全相同。在测试时,您应该争取正确性。执行速度排在第二位。

【讨论】:

我认为你错过了我的目的。我不是在寻找一个假的 Mongo 实例,我想要一个真实的实例,但嵌入到我的测试中。原因是启动 MongoDB 并将其置于特定状态而不会污染现有数据库,运行一系列操作,然后检查结果,而无需筛选与我的测试无关的任意数据。在保持受控测试环境的同时尽可能真实。 对不起,“模拟”这个词和所有这些“内存中”的建议让我忘记了“嵌入”在 Java 领域的含义。很高兴听到它。【参考方案6】:

使用 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

【讨论】:

你能解释一下“@EnableAutoConfiguration(exclude = EmbeddedMongoAutoConfiguration.class )”注解实际上在做什么吗? 原因很可能是 de.flapdoodle.embed.mongo 依赖项未标记为测试范围。不要在生产应用程序设置中选择并运行嵌入式 mongo,需要排除。【参考方案7】:

从 3.2.6 版开始,您可以在内存中运行 MongoDB。来自site:

从 MongoDB Enterprise 版本 3.2.6 开始,内存存储 引擎是 64 位版本中通用可用性 (GA) 的一部分。 除了一些元数据和诊断数据,内存存储 引擎不维护任何磁盘数据,包括配置 数据、索引、用户凭据等

【讨论】:

【参考方案8】:

这是accepted answer from @rozky 的更新(2019 年)版本(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.IMongodConfig;
import de.flapdoodle.embed.mongo.config.MongodConfigBuilder;
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;
        IMongodConfig mongodConfig = new MongodConfigBuilder()
        .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());
    


【讨论】:

对于每个测试重复启动和停止嵌入式 mongo 会导致大多数测试失败。最好在所有测试之前启动并在所有测试都执行后关闭 您需要在上面的更改中包含@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) @DBS 您也可以使用随机端口,这样您仍然可以在新的嵌入式 mongo 实例上同时运行测试。请参阅文档here。 是的,这就像一个魅力。从 baeldung 来到这里 - 这个例子一直在抛出 原因:java.lang.IllegalArgumentException:数据库名称不能为空!虽然我确实在 (MongoClients.create(String.format(CONNECTION_STRING, ip, port)), "cipresale");【参考方案9】:

不仅用于单元测试,还解释了如何将inmemory mongodb与rest api一起使用。

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

公共接口 UserRepository 扩展 MongoRepository

供参考,所有java代码使用以下链接:(逐步解释)

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

【讨论】:

【参考方案10】:

使用storageEngine='ephemeralForTest' 执行mongod 时性能更好

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

【讨论】:

【参考方案11】:

要运行嵌入式 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

【讨论】:

以上是关于运行集成测试时嵌入 MongoDB的主要内容,如果未能解决你的问题,请参考以下文章

在使用 Springboot 运行集成测试时启动嵌入式 gRPC 服务器

多语言堆栈的集成测试(Java/MongoDB/RabbitMQ...)

如何使用测试资源运行嵌入式 TomEE 进行集成测试

如何在 Maven 3 中运行嵌入式 Tomcat 9 以进行集成测试?

嵌入式 C++ 系统中的持续集成/单元测试

如何使用带有 gradle 的多个嵌入式服务器运行 spring-boot 集成测试