You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 08:35:11