如何Mock MongoDatabase的listCollectionNames方法编写单元测试
问题背景
现有一个引入spring-data-mongo依赖的Spring Boot项目,项目运行状态正常。其中一个服务的功能为:对指定Mongo数据库执行listCollections操作,遍历清空该库下所有集合的内容,对应业务代码如下:
public void clearContentsOfAllCollections() { MongoDatabase db = this.mongoTemplate.getMongoDbFactory().getDb("any-db-name"); LOGGER.info("================================================================="); LOGGER.info("Clearing collections in DB - {}", db.getName()); MongoIterable<String> collectionNames = db.listCollectionNames(); for (final String collectionName : collectionNames) { LOGGER.info("Clearing collection - {}", collectionName); db.getCollection(collectionName).deleteMany(new Document()); } LOGGER.info("Successfully cleared DB - {}", db.getName()); LOGGER.info("================================================================="); }
编写该方法的单元测试时,已完成部分Mock逻辑编写,测试代码如下:
@Test public void dropDBTest() { SimpleMongoClientDbFactory simpleMongoClientDbFactory = Mockito.mock(SimpleMongoClientDbFactory.class); MongoDatabase mongoDatabase = Mockito.mock(MongoDatabase.class); Mockito.when(mongoTemplate.getMongoDbFactory()).thenReturn(simpleMongoClientDbFactory); Mockito.when(simpleMongoClientDbFactory.getDb("db-name")) .thenReturn(mongoDatabase); // How do I convert this array list into a Mongo iterable List<String> collectionList = Arrays.asList("collection-1", "collection-2"); }
待解决问题
- 无法将构造好的普通字符串List转换为
MongoIterable<String>类型,无法Mockcom.mongodb.client包下database.listCollectionNames()方法的返回值 MongoIterable的iterator()方法返回类型为MongoCursor,不清楚该部分逻辑如何处理才能正确完成单元测试
解决方案
不需要手动实现MongoIterable、MongoCursor接口的全部方法,直接通过Mockito分层Mock即可,利用普通List的迭代器驱动Mock游标的遍历行为,写法兼容所有主流版本的Mongo Java驱动和Mockito。
注意原有测试代码存在一个容易忽略的问题:业务代码调用getDb传入的参数是any-db-name,Mock时写的参数是db-name,参数不匹配会导致Mock规则不生效,需要统一参数,或者直接用Mockito.anyString()做宽松匹配。
完整可运行的测试代码如下:
@Test public void dropDBTest() { // 初始化基础Mock对象 SimpleMongoClientDbFactory simpleMongoClientDbFactory = Mockito.mock(SimpleMongoClientDbFactory.class); MongoDatabase mongoDatabase = Mockito.mock(MongoDatabase.class); MongoCollection<Document> coll1 = Mockito.mock(MongoCollection.class); MongoCollection<Document> coll2 = Mockito.mock(MongoCollection.class); // 构造测试用集合名列表 List<String> collectionList = Arrays.asList("collection-1", "collection-2"); Iterator<String> listIterator = collectionList.iterator(); // Mock MongoCursor,复用普通List的迭代器逻辑 MongoCursor<String> mockCursor = Mockito.mock(MongoCursor.class); Mockito.when(mockCursor.hasNext()).thenAnswer(inv -> listIterator.hasNext()); Mockito.when(mockCursor.next()).thenAnswer(inv -> listIterator.next()); // Mock MongoIterable,绑定游标返回 MongoIterable<String> mockCollNames = Mockito.mock(MongoIterable.class); Mockito.when(mockCollNames.iterator()).thenReturn(mockCursor); // 绑定所有Mock返回规则 Mockito.when(mongoTemplate.getMongoDbFactory()).thenReturn(simpleMongoClientDbFactory); Mockito.when(simpleMongoClientDbFactory.getDb("any-db-name")).thenReturn(mongoDatabase); Mockito.when(mongoDatabase.getName()).thenReturn("any-db-name"); Mockito.when(mongoDatabase.listCollectionNames()).thenReturn(mockCollNames); Mockito.when(mongoDatabase.getCollection("collection-1")).thenReturn(coll1); Mockito.when(mongoDatabase.getCollection("collection-2")).thenReturn(coll2); // 执行被测方法 clearService.clearContentsOfAllCollections(); // 验证执行结果:两个集合都执行了deleteMany操作 Mockito.verify(coll1).deleteMany(Mockito.any(Document.class)); Mockito.verify(coll2).deleteMany(Mockito.any(Document.class)); }
如果使用的是Mongo Java Driver 4.x版本,也可以直接用驱动自带的MongoIterable实现类简化代码,不需要手动Mock游标:
// 4.x版本可直接用该方式构造MongoIterable MongoIterable<String> mockCollNames = ListMongoIterable.of(collectionList);
内容的提问来源于stack exchange,提问作者Anirudh Ramesh
相关产品推荐
相关产品推荐

