簡介
前面的文章中,我們介紹了如何在SpringBoot 中使用MongoDB的一些常用技巧。
那么,與使用其他數(shù)據(jù)庫如 MySQL 一樣,我們應(yīng)該怎么來做MongoDB的單元測試呢?
使用內(nèi)嵌數(shù)據(jù)庫的好處是不需要依賴于一個外部環(huán)境,如果每一次跑單元測試都需要依賴一個穩(wěn)定的外部環(huán)境,那么這樣的測試是極不穩(wěn)定的。
為了更歡快的使用MongoDB,這里提供兩種使用內(nèi)嵌數(shù)據(jù)庫做單元測試的方式。
一、使用 flapdoodle.embed.mongo
開源地址
該組件的大致原理是,在當(dāng)前環(huán)境中自動下載MongoDB并拉起進(jìn)程,測試后再做關(guān)閉。
先演示一遍如何使用:
A. 引入依賴
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<version>1.50.5</version>
<scope>test</scope>
</dependency>
B. 準(zhǔn)備測試類
編寫一個基礎(chǔ)類:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = DemoBoot.class)
@ActiveProfiles("test")
public class BaseEmbededMongoTest {
private static final Logger logger = LoggerFactory.getLogger(BaseEmbededMongoTest.class);
protected static final MongodStarter starter = MongodStarter.getDefaultInstance();
protected static MongodExecutable _mongodExe;
protected static MongodProcess _mongod;
// 確保與配置一致
protected static final String host = "127.0.0.1";
protected static final int port = 27027;
@BeforeClass
public static void setUp() throws Exception {
_mongodExe = starter.prepare(new MongodConfigBuilder().version(Version.Main.PRODUCTION)
.net(new Net(host, port, Network.localhostIsIPv6())).build());
_mongod = _mongodExe.start();
logger.info("mongod started on {}:{}", host, port);
}
@AfterClass
public static void tearDown() throws Exception {
_mongod.stop();
_mongodExe.stop();
}
}
BaseEmbededMongoTest 實現(xiàn)了:
測試啟動前啟動MongoDB進(jìn)程;
測試完成后關(guān)閉MongoDB進(jìn)程;
讓業(yè)務(wù)測試類繼承于基礎(chǔ)類:
public class BookServiceTest extends BaseEmbededMongoTest{
@Autowired
private BookService bookService;
@Autowired
private BookRepository bookRepository;
...
C. 完善配置
為了避免沖突,需要關(guān)閉EmbeddedMongoAutoConfiguration。
@SpringBootApplication
@EnableAutoConfiguration(exclude = { EmbeddedMongoAutoConfiguration.class})
public class BootSampleMongo {
...
最后一步,為了讓業(yè)務(wù)代碼能連接到自啟動的MongoDB,需要做對應(yīng)的配置:
在src/test/resources目錄中編輯 application-test.properties
spring.data.mongodb.host=localhost
spring.data.mongodb.port=27027
spring.data.mongodb.database=test
D. 啟動測試
執(zhí)行業(yè)務(wù)測試類,可以看到一系列輸出:
//下載
Download PRODUCTION:Windows:B64 START
Download PRODUCTION:Windows:B64 DownloadSize: 147911698
Download PRODUCTION:Windows:B64 0% 1% 2% 3% 4% 5% 6% 7% 8% 9% 10% 11%
...
//啟動繼承
[mongod output] 2019-03-02T15:43:01.948+0800 I CONTROL [initandlisten] db version v3.2.1
[mongod output] 2019-03-02T15:43:01.948+0800 I CONTROL [initandlisten] git version: a14d55980c2cdc565d4704a7e3ad37e4e535c1b2
[mongod output] 2019-03-02T15:43:01.948+0800 I CONTROL [initandlisten] allocator: tcmalloc
[mongod output] 2019-03-02T15:43:01.948+0800 I CONTROL [initandlisten] modules: none
[mongod output] 2019-03-02T15:43:01.948+0800 I CONTROL [initandlisten] build environment:
[mongod output] 2019-03-02T15:43:01.948+0800 I CONTROL [initandlisten] distmod: 2008plus
[mongod output] 2019-03-02T15:43:01.948+0800 I CONTROL [initandlisten] distarch: x86_64
[mongod output] 2019-03-02T15:43:01.948+0800 I CONTROL [initandlisten] target_arch: x86_64
...
[mongod output] 2019-03-02T15:43:02.070+0800 I NETWORK [initandlisten] waiting for connections on port 27027
//單元測試
...
//關(guān)閉進(jìn)程
[mongod output] 2019-03-02T15:43:20.838+0800 I COMMAND [conn3] terminating, shutdown command received
[mongod output] 2019-03-02T15:43:20.838+0800 I FTDC [conn3] Shutting down full-time diagnostic data capture
[mongod output] 2019-03-02T15:43:20.846+0800 I CONTROL [conn3] now exiting
[mongod output] 2019-03-02T15:43:20.846+0800 I NETWORK [conn3] shutdown: going to close listening sockets...
[mongod output] 2019-03-02T15:43:20.846+0800 I NETWORK [conn3] closing listening socket: 456
[mongod output] 2019-03-02T15:43:20.846+0800 I NETWORK [conn3] shutdown: going to flush diaglog...
[mongod output] 2019-03-02T15:43:20.846+0800 I NETWORK [conn3] shutdown: going to close sockets...
[mongod output] 2019-03-02T15:43:20.911+0800 I NETWORK [conn1] end connection 127.0.0.1:52319 (2 connections now open)
[mongod output] 2019-03-02T15:43:20.911+0800 I STORAGE [conn3] WiredTigerKVEngine shutting down
[mongod output] 2019-03-02T15:43:20.916+0800 I NETWORK [conn2] end connection 127.0.0.1:52320 (1 connection now open)
[mongod output] 2019-03-02T15:43:20.943+0800 I STORAGE [conn3] shutdown: removing fs lock...
[mongod output] 2019-03-02T15:43:20.943+0800 I CONTROL [conn3] dbexit: rc: 0
注:首次使用該組件時需要下載安裝包,過程比較緩慢需要些耐心..
細(xì)節(jié)
細(xì)心的同學(xué)可能注意到了,我們?yōu)槭裁匆貏e規(guī)避EmbeddedMongoAutoConfiguration這個類呢?
在SpringBoot 官方文檔中提到了 EmbeddedMongoAutoConfiguration,其作用主要是:
自動檢測 flapdoodle.embed.mongo組件是否被引入;
如果當(dāng)前的運行環(huán)境中能找到組件,則會自動啟動組件,并在程序退出時做銷毀
我們簡單看一下其實現(xiàn):
@Configuration
@EnableConfigurationProperties({ MongoProperties.class, EmbeddedMongoProperties.class })
@AutoConfigureBefore(MongoAutoConfiguration.class)
@ConditionalOnClass({ Mongo.class, MongodStarter.class })
public class EmbeddedMongoAutoConfiguration {
private final MongoProperties properties;
private final EmbeddedMongoProperties embeddedProperties;
@Bean(initMethod = "start", destroyMethod = "stop")
@ConditionalOnMissingBean
public MongodExecutable embeddedMongoServer(IMongodConfig mongodConfig)
throws IOException {
Integer configuredPort = this.properties.getPort();
if (configuredPort == null || configuredPort == 0) {
setEmbeddedPort(mongodConfig.net().getPort());
}
MongodStarter mongodStarter = getMongodStarter(this.runtimeConfig);
return mongodStarter.prepare(mongodConfig);
}
不難猜到,該配置類已經(jīng)完成了我們在單元測試中所需要的一切事情,那為什么還需要BaseEmbededMongoTest?
答案在于,我們可能會對MongoDB的連接池做許多定制,如下面的代碼:
@Configuration
public void MongoConfig{
@Bean
public MongoDbFactory mongoDbFactory(){
...
}
}
類似這樣的定制,會讓MongoAutoConfiguration失效。即SpringDataMongo 的初始化會先于Embeded實例的啟動,導(dǎo)致失敗。
通過自定義的實現(xiàn)則可以規(guī)避該問題,當(dāng)然如果通過Profile設(shè)定也可以進(jìn)行規(guī)避。
二、使用Fongo
開源地址
Fongo 是由 Fousquare 開發(fā)團(tuán)隊開源的一款真正的內(nèi)存式MongoDB,非常適用于輕量級的單元測試。
這個名字.. 不錯哈
Fongo 支持對Java-Driver的各種CRUD指令進(jìn)行解析,并模擬數(shù)據(jù)在內(nèi)存中的存儲管理操作,可以認(rèn)為其提供了一層JavaDriver的代理。
同時,該框架是線程安全的,所有的集合讀寫操作都能得到同步保護(hù)
接下來是如何使用:
A. 引入框架
<!-- fongo face mongo -->
<dependency>
<groupId>com.github.fakemongo</groupId>
<artifactId>fongo</artifactId>
<version>2.1.0</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</exclusion>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</exclusion>
</exclusions>
</dependency>
注:fongo依賴于jackson,可能與SpringBoot項目沖突,這里顯示將其剔除。
B. 準(zhǔn)備測試類
編寫一個基于Fongo的類:
@ActiveProfiles("test")
@RunWith(SpringRunner.class)
@SpringBootTest(classes = BootSampleMongo.class)
@Import(TestConfig.class)
public class BaseFongoTest {
}
這里使用@Import導(dǎo)入了一個TestConfig,用于初始化Fongo實例,如下:
@TestConfiguration
@Profile("test")
public class TestConfig extends AbstractMongoConfiguration {
@Autowired
private Environment env;
@Override
protected String getDatabaseName() {
return env.getProperty("spring.data.mongodb.database", "test");
}
@Override
public Mongo mongo() throws Exception {
return new Fongo(getDatabaseName()).getMongo();
}
}
這樣,通過繼承于AbstractMongoConfiguration,可以省去配置MongoDbFactory之類的工作。
需要注意的是,如果業(yè)務(wù)代碼做了一些連接池的定制,如MongoDbFactory/MongoTemplate的定義,則需要通過Profile進(jìn)行隔離,避免在測試過程中出錯:
@Configuration
@Profile("prod")
public class ProdMongoConfig {
...
C.業(yè)務(wù)測試
準(zhǔn)備好上面的工作后,則可以用到業(yè)務(wù)測試代碼上:
public class BookServiceTest extends BaseFongoTest{
@Autowired
private BookService bookService;
@Autowired
private BookRepository bookRepository;
至此,我們已經(jīng)完成了Fongo 的使用。
碼云同步代碼
參考文檔
springboot-with-mongo-embed
flapdoodle-embed-mongo-github
another-embededmongo-fongo
小結(jié)
隨著MongoDB 在Web開發(fā)中的應(yīng)用越來越廣,許多配套的框架及工具也在逐步完善。
本文介紹了兩種在SpringBoot 框架上使用內(nèi)嵌MongoDB的方式,從簡易性來看,個人更推薦Fongo的方案。
由于Fongo 更接近于H2(一種內(nèi)存SQL數(shù)據(jù)庫)的實現(xiàn),整個測試過程中不需要開啟MongoDB進(jìn)程,也免去了遠(yuǎn)程下載軟件的煩惱。
所有的操作均在內(nèi)存中完成,會令整個測試更加的高效,然而其僅有的缺點是無法支持一些原生的MongoDB管理命令(一般也不會用到)。
當(dāng)然,讀者也可以根據(jù)自己的需求自行選擇。
本站文章版權(quán)歸原作者及原出處所有 。內(nèi)容為作者個人觀點, 并不代表本站贊同其觀點和對其真實性負(fù)責(zé),本站只提供參考并不構(gòu)成任何投資及應(yīng)用建議。本站是一個個人學(xué)習(xí)交流的平臺,網(wǎng)站上部分文章為轉(zhuǎn)載,并不用于任何商業(yè)目的,我們已經(jīng)盡可能的對作者和來源進(jìn)行了通告,但是能力有限或疏忽,造成漏登,請及時聯(lián)系我們,我們將根據(jù)著作權(quán)人的要求,立即更正或者刪除有關(guān)內(nèi)容。本站擁有對此聲明的最終解釋權(quán)。