前面的文章中,我们介绍了如何在SpringBoot 中使用MongoDB的一些常用技巧。
那么,与使用其他数据库如 MySQL 一样,我们应该怎么来做MongoDB的单元测试呢?

使用内嵌数据库的好处是不需要依赖于一个外部环境,如果每一次跑单元测试都需要依赖一个稳定的外部环境,那么这样的测试是极不稳定的。
为了更欢快的使用MongoDB,这里提供两种使用内嵌数据库做单元测试的方式。

开源地址
该组件的大致原理是,在当前环境中自动下载MongoDB并拉起进程,测试后再做关闭。
先演示一遍如何使用:

  1. <dependency>
  2. <groupId>de.flapdoodle.embed</groupId>
  3. <artifactId>de.flapdoodle.embed.mongo</artifactId>
  4. <version>1.50.5</version>
  5. <scope>test</scope>
  6. </dependency>

编写一个基础类:

  1. @RunWith(SpringRunner.class)
  2. @SpringBootTest(classes = DemoBoot.class)
  3. @ActiveProfiles("test")
  4. public class BaseEmbededMongoTest {
  5. private static final Logger logger = LoggerFactory.getLogger(BaseEmbededMongoTest.class);
  6. protected static final MongodStarter starter = MongodStarter.getDefaultInstance();
  7. protected static MongodExecutable _mongodExe;
  8. protected static MongodProcess _mongod;
  9. // 确保与配置一致
  10. protected static final String host = "127.0.0.1";
  11. protected static final int port = 27027;
  12. @BeforeClass
  13. public static void setUp() throws Exception {
  14. _mongodExe = starter.prepare(new MongodConfigBuilder().version(Version.Main.PRODUCTION)
  15. .net(new Net(host, port, Network.localhostIsIPv6())).build());
  16. _mongod = _mongodExe.start();
  17. logger.info("mongod started on {}:{}", host, port);
  18. }
  19. @AfterClass
  20. public static void tearDown() throws Exception {
  21. _mongod.stop();
  22. _mongodExe.stop();
  23. }
  24. }

BaseEmbededMongoTest 实现了:

  • 测试启动前启动MongoDB进程;
  • 测试完成后关闭MongoDB进程;

让业务测试类继承于基础类:

  1. public class BookServiceTest extends BaseEmbededMongoTest{
  2. @Autowired
  3. private BookService bookService;
  4. @Autowired
  5. private BookRepository bookRepository;
  6. ...

为了避免冲突,需要关闭EmbeddedMongoAutoConfiguration

  1. @SpringBootApplication
  2. @EnableAutoConfiguration(exclude = { EmbeddedMongoAutoConfiguration.class})
  3. public class BootSampleMongo {
  4. ...

最后一步,为了让业务代码能连接到自启动的MongoDB,需要做对应的配置:

在src/test/resources目录中编辑 application-test.properties

  1. spring.data.mongodb.host=localhost
  2. spring.data.mongodb.port=27027
  3. spring.data.mongodb.database=test

执行业务测试类,可以看到一系列输出:

  1. //下载
  2. Download PRODUCTION:Windows:B64 START
  3. Download PRODUCTION:Windows:B64 DownloadSize: 147911698
  4. Download PRODUCTION:Windows:B64 0% 1% 2% 3% 4% 5% 6% 7% 8% 9% 10% 11%
  5. ...
  6. //启动继承
  7. [mongod output] 2019-03-02T15:43:01.948+0800 I CONTROL [initandlisten] db version v3.2.1
  8. [mongod output] 2019-03-02T15:43:01.948+0800 I CONTROL [initandlisten] git version: a14d55980c2cdc565d4704a7e3ad37e4e535c1b2
  9. [mongod output] 2019-03-02T15:43:01.948+0800 I CONTROL [initandlisten] allocator: tcmalloc
  10. [mongod output] 2019-03-02T15:43:01.948+0800 I CONTROL [initandlisten] modules: none
  11. [mongod output] 2019-03-02T15:43:01.948+0800 I CONTROL [initandlisten] build environment:
  12. [mongod output] 2019-03-02T15:43:01.948+0800 I CONTROL [initandlisten] distmod: 2008plus
  13. [mongod output] 2019-03-02T15:43:01.948+0800 I CONTROL [initandlisten] distarch: x86_64
  14. [mongod output] 2019-03-02T15:43:01.948+0800 I CONTROL [initandlisten] target_arch: x86_64
  15. ...
  16. [mongod output] 2019-03-02T15:43:02.070+0800 I NETWORK [initandlisten] waiting for connections on port 27027
  17. //单元测试
  18. ...
  19. //关闭进程
  20. [mongod output] 2019-03-02T15:43:20.838+0800 I COMMAND [conn3] terminating, shutdown command received
  21. [mongod output] 2019-03-02T15:43:20.838+0800 I FTDC [conn3] Shutting down full-time diagnostic data capture
  22. [mongod output] 2019-03-02T15:43:20.846+0800 I CONTROL [conn3] now exiting
  23. [mongod output] 2019-03-02T15:43:20.846+0800 I NETWORK [conn3] shutdown: going to close listening sockets...
  24. [mongod output] 2019-03-02T15:43:20.846+0800 I NETWORK [conn3] closing listening socket: 456
  25. [mongod output] 2019-03-02T15:43:20.846+0800 I NETWORK [conn3] shutdown: going to flush diaglog...
  26. [mongod output] 2019-03-02T15:43:20.846+0800 I NETWORK [conn3] shutdown: going to close sockets...
  27. [mongod output] 2019-03-02T15:43:20.911+0800 I NETWORK [conn1] end connection 127.0.0.1:52319 (2 connections now open)
  28. [mongod output] 2019-03-02T15:43:20.911+0800 I STORAGE [conn3] WiredTigerKVEngine shutting down
  29. [mongod output] 2019-03-02T15:43:20.916+0800 I NETWORK [conn2] end connection 127.0.0.1:52320 (1 connection now open)
  30. [mongod output] 2019-03-02T15:43:20.943+0800 I STORAGE [conn3] shutdown: removing fs lock...
  31. [mongod output] 2019-03-02T15:43:20.943+0800 I CONTROL [conn3] dbexit: rc: 0

注:首次使用该组件时需要下载安装包,过程比较缓慢需要些耐心..

细心的同学可能注意到了,我们为什么要特别规避EmbeddedMongoAutoConfiguration这个类呢?

在SpringBoot 官方文档中提到了 EmbeddedMongoAutoConfiguration,其作用主要是:

  • 自动检测 flapdoodle.embed.mongo组件是否被引入;
  • 如果当前的运行环境中能找到组件,则会自动启动组件,并在程序退出时做销毁

我们简单看一下其实现:

  1. @Configuration
  2. @EnableConfigurationProperties({ MongoProperties.class, EmbeddedMongoProperties.class })
  3. @AutoConfigureBefore(MongoAutoConfiguration.class)
  4. @ConditionalOnClass({ Mongo.class, MongodStarter.class })
  5. public class EmbeddedMongoAutoConfiguration {
  6. private final MongoProperties properties;
  7. private final EmbeddedMongoProperties embeddedProperties;
  8. @Bean(initMethod = "start", destroyMethod = "stop")
  9. @ConditionalOnMissingBean
  10. public MongodExecutable embeddedMongoServer(IMongodConfig mongodConfig)
  11. throws IOException {
  12. Integer configuredPort = this.properties.getPort();
  13. if (configuredPort == null || configuredPort == 0) {
  14. setEmbeddedPort(mongodConfig.net().getPort());
  15. }
  16. MongodStarter mongodStarter = getMongodStarter(this.runtimeConfig);
  17. return mongodStarter.prepare(mongodConfig);
  18. }

不难猜到,该配置类已经完成了我们在单元测试中所需要的一切事情,那为什么还需要BaseEmbededMongoTest?
答案在于,我们可能会对MongoDB的连接池做许多定制,如下面的代码:

  1. @Configuration
  2. public void MongoConfig{
  3. @Bean
  4. public MongoDbFactory mongoDbFactory(){
  5. ...
  6. }
  7. }

类似这样的定制,会让MongoAutoConfiguration失效。即SpringDataMongo 的初始化会先于Embeded实例的启动,导致失败。
通过自定义的实现则可以规避该问题,当然如果通过Profile设定也可以进行规避。

开源地址
Fongo 是由 Fousquare 开发团队开源的一款真正的内存式MongoDB,非常适用于轻量级的单元测试。
这个名字.. 不错哈

Fongo 支持对Java-Driver的各种CRUD指令进行解析,并模拟数据在内存中的存储管理操作,可以认为其提供了一层JavaDriver的代理。
同时,该框架是线程安全的,所有的集合读写操作都能得到同步保护

接下来是如何使用:

  1. <!-- fongo face mongo -->
  2. <dependency>
  3. <groupId>com.github.fakemongo</groupId>
  4. <artifactId>fongo</artifactId>
  5. <version>2.1.0</version>
  6. <scope>test</scope>
  7. <exclusions>
  8. <exclusion>
  9. <groupId>com.fasterxml.jackson.core</groupId>
  10. <artifactId>jackson-core</artifactId>
  11. </exclusion>
  12. <exclusion>
  13. <groupId>com.fasterxml.jackson.core</groupId>
  14. <artifactId>jackson-databind</artifactId>
  15. </exclusion>
  16. </exclusions>
  17. </dependency>

注:fongo依赖于jackson,可能与SpringBoot项目冲突,这里显示将其剔除。

编写一个基于Fongo的类:

  1. @ActiveProfiles("test")
  2. @RunWith(SpringRunner.class)
  3. @SpringBootTest(classes = BootSampleMongo.class)
  4. @Import(TestConfig.class)
  5. public class BaseFongoTest {
  6. }

这里使用@Import导入了一个TestConfig,用于初始化Fongo实例,如下:

  1. @TestConfiguration
  2. @Profile("test")
  3. public class TestConfig extends AbstractMongoConfiguration {
  4. @Autowired
  5. private Environment env;
  6. @Override
  7. protected String getDatabaseName() {
  8. return env.getProperty("spring.data.mongodb.database", "test");
  9. }
  10. @Override
  11. public Mongo mongo() throws Exception {
  12. return new Fongo(getDatabaseName()).getMongo();
  13. }
  14. }

这样,通过继承于AbstractMongoConfiguration,可以省去配置MongoDbFactory之类的工作。
需要注意的是,如果业务代码做了一些连接池的定制,如MongoDbFactory/MongoTemplate的定义,则需要通过Profile进行隔离,避免在测试过程中出错:

  1. @Configuration
  2. @Profile("prod")
  3. public class ProdMongoConfig {
  4. ...

准备好上面的工作后,则可以用到业务测试代码上:

  1. public class BookServiceTest extends BaseFongoTest{
  2. @Autowired
  3. private BookService bookService;
  4. @Autowired
  5. private BookRepository bookRepository;

至此,我们已经完成了Fongo 的使用。

springboot-with-mongo-embed
flapdoodle-embed-mongo-github
another-embededmongo-fongo

随着MongoDB 在Web开发中的应用越来越广,许多配套的框架及工具也在逐步完善。
本文介绍了两种在SpringBoot 框架上使用内嵌MongoDB的方式,从简易性来看,个人更推荐Fongo的方案。
由于Fongo 更接近于H2(一种内存SQL数据库)的实现,整个测试过程中不需要开启MongoDB进程,也免去了远程下载软件的烦恼。
所有的操作均在内存中完成,会令整个测试更加的高效,然而其仅有的缺点是无法支持一些原生的MongoDB管理命令(一般也不会用到)。
当然,读者也可以根据自己的需求自行选择。

欢迎继续关注”美码师的补习系列-springboot篇” ,期待更多精彩内容^-^

版权声明:本文为littleatp原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/littleatp/p/10462395.html