Couchbase 7.2升级后Java数据库调用代码的测试覆盖问题
问题解决:Mockito无法Mock Couchbase的Scope和Collection类
问题根源
Couchbase Java SDK 7.x版本中的com.couchbase.client.java.Scope和com.couchbase.client.java.Collection是final类,而Mockito默认不能mock final类。你修改自己的CouchbaseConfigProperties配置类无法解决这个问题,因为问题出在SDK自带的类上,而非你的自定义配置。
解决方案
以下三种方案可以解决这个问题,按推荐优先级排序:
方案1:启用Mockito的Final Mock特性(最简单直接)
Mockito 2及以上版本支持mock final类,但需要添加mockito-inline依赖并启用该特性。
步骤1:添加依赖(Maven示例)
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-inline</artifactId> <version>4.0.0</version> <!-- 使用与项目匹配的Mockito版本 --> <scope>test</scope> </dependency>
步骤2:修改测试代码
@RunWith(MockitoJUnitRunner.class) public class AccessServiceInventoryRepositoryImplTest { @Mock private Bucket bucket; @Mock private Cluster cluster; @Mock private Scope scope; // 新增Scope mock @Mock private Collection collection; // 新增Collection mock @Mock private GetResult getResult; @Mock private ServiceInventoryMethodsValidator serviceInventoryMethodsValidator; private AccessServiceInventoryRepositoryImpl repositoryImpl; @Before public void setup() throws NoSuchFieldException, IllegalAccessException { repositoryImpl = new AccessServiceInventoryRepositoryImpl( serviceInventoryMethodsValidator, CouchbaseBucketConfig.getCouchbaseConfig(), CouchbaseBucketConfig.getMicroserviceConfigProperties(), new DomainServiceInventoryConfigProperties()); ReflectionTestUtils.setField(repositoryImpl, "cluster", cluster); final Field bucketField = ServiceInventoryRepositoryImpl.class.getDeclaredField("bucket"); bucketField.setAccessible(true); bucketField.set(repositoryImpl, bucket); // 替换原defaultCollection的mock逻辑 when(bucket.scope(anyString())).thenReturn(scope); when(scope.collection(anyString())).thenReturn(collection); when(collection.get(anyString())).thenReturn(getResult); } @Test(expected = ServiceInventoryException.class) public void getServiceInventoryByIdShouldThrowServiceInventoryExceptionWhenJsonDocumentIsNull() throws Exception { // 直接mockCollection的get方法返回null when(collection.get(anyString())).thenReturn(null); repositoryImpl.getServiceInventoryById("123456"); } }
方案2:使用Couchbase官方测试工具类
Couchbase提供了MockBucket等测试组件,可以模拟真实的Bucket、Scope和Collection行为,无需依赖Mockito mock SDK类。
@Before public void setup() throws NoSuchFieldException, IllegalAccessException { // 创建MockBucket实例 MockBucket mockBucket = MockBucket.create(); // 创建测试用的Scope和Collection Scope testScope = mockBucket.scope("test-scope"); Collection testCollection = testScope.collection("test-collection"); repositoryImpl = new AccessServiceInventoryRepositoryImpl( serviceInventoryMethodsValidator, CouchbaseBucketConfig.getCouchbaseConfig(), CouchbaseBucketConfig.getMicroserviceConfigProperties(), new DomainServiceInventoryConfigProperties()); ReflectionTestUtils.setField(repositoryImpl, "cluster", mockBucket.cluster()); final Field bucketField = ServiceInventoryRepositoryImpl.class.getDeclaredField("bucket"); bucketField.setAccessible(true); bucketField.set(repositoryImpl, mockBucket); // 使用Spy包装Collection,修改get方法返回值 Collection spyCollection = spy(testCollection); when(spyCollection.get(anyString())).thenReturn(null); // 替换MockBucket中的Collection(需通过反射或MockBucket API实现) }
方案3:封装底层数据访问逻辑(解耦SDK依赖)
将Couchbase的Scope、Collection操作封装到自定义接口中,测试时只mock自己的接口,彻底避免直接依赖SDK的final类。
步骤1:定义数据访问接口
public interface CouchbaseDataAccessor { GetResult getDocument(String scopeName, String collectionName, String docId); }
步骤2:实现接口
@Component public class CouchbaseDataAccessorImpl implements CouchbaseDataAccessor { private final Bucket bucket; @Autowired public CouchbaseDataAccessorImpl(Bucket bucket) { this.bucket = bucket; } @Override public GetResult getDocument(String scopeName, String collectionName, String docId) { return bucket.scope(scopeName).collection(collectionName).get(docId); } }
步骤3:修改Repository类
@Primary @Repository public class AccessServiceInventoryRepositoryImpl extends ServiceInventoryRepositoryImpl implements AccessServiceInventoryRepository { private final Logger log = LoggerFactory.getLogger(getClass()); private final Gson gson = new Gson(); private final CouchbaseDataAccessor dataAccessor; // 新增依赖 @Autowired public AccessServiceInventoryRepositoryImpl(final ServiceInventoryMethodsValidator serviceInventoryMethodsValidator, final CouchbaseConfigProperties couchbaseConfigProperties, final MicroserviceConfigProperties microserviceConfigProperties, final DomainServiceInventoryConfigProperties domainServiceInventoryConfigProperties, final CouchbaseDataAccessor dataAccessor) { super(serviceInventoryMethodsValidator, couchbaseConfigProperties, microserviceConfigProperties, domainServiceInventoryConfigProperties); this.dataAccessor = dataAccessor; } @Override public AccessServiceInventory getServiceInventoryById(final String serviceInstanceId) throws ServiceInventoryException { log.debug("{} retrieving service inventory by ID {}", getClass().getSimpleName(), getDocumentId(serviceInstanceId)); final GetResult serviceInventoryJsonDocument = dataAccessor.getDocument(bucketScope, bucketCollection, getDocumentId(serviceInstanceId)); if (serviceInventoryJsonDocument == null) { throw new ServiceInventoryException("No results returned, processing timeout."); } return gson.fromJson(serviceInventoryJsonDocument.contentAsObject().toString(), AccessServiceInventory.class); } }
步骤4:修改测试类
@RunWith(MockitoJUnitRunner.class) public class AccessServiceInventoryRepositoryImplTest { @Mock private CouchbaseDataAccessor dataAccessor; @Mock private ServiceInventoryMethodsValidator serviceInventoryMethodsValidator; private AccessServiceInventoryRepositoryImpl repositoryImpl; @Before public void setup() { repositoryImpl = new AccessServiceInventoryRepositoryImpl( serviceInventoryMethodsValidator, CouchbaseBucketConfig.getCouchbaseConfig(), CouchbaseBucketConfig.getMicroserviceConfigProperties(), new DomainServiceInventoryConfigProperties(), dataAccessor); } @Test(expected = ServiceInventoryException.class) public void getServiceInventoryByIdShouldThrowServiceInventoryExceptionWhenJsonDocumentIsNull() throws Exception { when(dataAccessor.getDocument(anyString(), anyString(), anyString())).thenReturn(null); repositoryImpl.getServiceInventoryById("123456"); } }
内容的提问来源于stack exchange,提问作者akshat rathore
相关产品推荐
相关产品推荐

