補習系列(17)-springboot mongodb 內嵌資料庫
簡介
前面的文章中,我們介紹瞭如何在SpringBoot 中使用MongoDB的一些常用技巧。
那麼,與使用其他資料庫如 MySQL 一樣,我們應該怎麼來做MongoDB的單元測試呢?
使用內嵌資料庫的好處是不需要依賴於一個外部環境,如果每一次跑單元測試都需要依賴一個穩定的外部環境,那麼這樣的測試是極不穩定的。
為了更歡快的使用MongoDB,這裡提供兩種使用內嵌資料庫做單元測試的方式。
一、使用 flapdoodle.embed.mongo
該元件的大致原理是,在當前環境中自動下載MongoDB並拉起程序,測試後再做關閉。
先演示一遍如何使用:
A. 引入依賴
<dependency> <groupId>de.flapdoodle.embed</groupId> <artifactId>de.flapdoodle.embed.mongo</artifactId> <version>1.50.5</version> <scope>test</scope> </dependency>
B. 準備測試類
編寫一個基礎類:
@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 實現了:
- 測試啟動前啟動MongoDB程序;
- 測試完成後關閉MongoDB程序;
讓業務測試類繼承於基礎類:
public class BookServiceTest extends BaseEmbededMongoTest{ @Autowired private BookService bookService; @Autowired private BookRepository bookRepository; ...
C. 完善配置
為了避免衝突,需要關閉EmbeddedMongoAutoConfiguration 。
@SpringBootApplication @EnableAutoConfiguration(exclude = { EmbeddedMongoAutoConfiguration.class}) public class BootSampleMongo { ...
最後一步,為了讓業務程式碼能連線到自啟動的MongoDB,需要做對應的配置:
在src/test/resources目錄中編輯 application-test.properties
spring.data.mongodb.host=localhost spring.data.mongodb.port=27027 spring.data.mongodb.database=test
D. 啟動測試
執行業務測試類,可以看到一系列輸出:
//下載 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 //單元測試 ... //關閉程序 [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
注:首次使用該元件時需要下載安裝包,過程比較緩慢需要些耐心..
細節
細心的同學可能注意到了,我們為什麼要特別規避EmbeddedMongoAutoConfiguration 這個類呢?
在SpringBoot 官方文件中提到了EmbeddedMongoAutoConfiguration ,其作用主要是:
- 自動檢測 flapdoodle.embed.mongo元件是否被引入;
- 如果當前的執行環境中能找到元件,則會自動啟動元件,並在程式退出時做銷燬
我們簡單看一下其實現:
@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); }
不難猜到,該配置類已經完成了我們在單元測試中所需要的一切事情,那為什麼還需要BaseEmbededMongoTest?
答案在於,我們可能會對MongoDB的連線池做許多定製,如下面的程式碼:
@Configuration public void MongoConfig{ @Bean public MongoDbFactory mongoDbFactory(){ ... } }
類似這樣的定製,會讓MongoAutoConfiguration失效。即SpringDataMongo 的初始化會先於Embeded例項的啟動,導致失敗。
通過自定義的實現則可以規避該問題,當然如果通過Profile設定也可以進行規避。
二、使用Fongo
Fongo 是由 Fousquare 開發團隊開源的一款真正的記憶體式MongoDB,非常適用於輕量級的單元測試。
這個名字.. 不錯哈
Fongo 支援對Java-Driver的各種CRUD指令進行解析,並模擬資料在記憶體中的儲存管理操作,可以認為其提供了一層JavaDriver的代理。
同時,該框架是執行緒安全的,所有的集合讀寫操作都能得到同步保護
接下來是如何使用:
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. 準備測試類
編寫一個基於Fongo的類:
@ActiveProfiles("test") @RunWith(SpringRunner.class) @SpringBootTest(classes = BootSampleMongo.class) @Import(TestConfig.class) public class BaseFongoTest { }
這裡使用@Import 匯入了一個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之類的工作。
需要注意的是,如果業務程式碼做了一些連線池的定製,如MongoDbFactory/MongoTemplate的定義,則需要通過Profile進行隔離,避免在測試過程中出錯:
@Configuration @Profile("prod") public class ProdMongoConfig { ...
C.業務測試
準備好上面的工作後,則可以用到業務測試程式碼上:
public class BookServiceTest extends BaseFongoTest{ @Autowired private BookService bookService; @Autowired private BookRepository bookRepository;
至此,我們已經完成了Fongo 的使用。
參考文件
小結
隨著MongoDB 在Web開發中的應用越來越廣,許多配套的框架及工具也在逐步完善。
本文介紹了兩種在SpringBoot 框架上使用內嵌MongoDB的方式,從簡易性來看,個人更推薦Fongo的方案。
由於Fongo 更接近於H2(一種記憶體SQL資料庫)的實現,整個測試過程中不需要開啟MongoDB程序,也免去了遠端下載軟體的煩惱。
所有的操作均在記憶體中完成,會令整個測試更加的高效,然而其僅有的缺點是無法支援一些原生的MongoDB管理命令(一般也不會用到)。
當然,讀者也可以根據自己的需求自行選擇。
歡迎繼續關注"美碼師的補習系列-springboot篇" ,期待更多精彩內容^-^