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

如何在Java中对Azure Key Vault(DefaultAzureCredentialBuilder)进行单元测试

如何Mock Azure Key Vault服务内部创建的SecretClient进行单元测试

原代码中SecretClient是在getSecret方法内部直接实例化的,这种硬编码的依赖创建方式导致无法直接用Mockito等工具mock该对象。要解决这个问题,核心是解耦依赖创建逻辑,让SecretClient的实例化可以被外部替换,下面提供两种常用方案:

方案一:直接依赖注入SecretClient

重构服务类,新增一个支持注入SecretClient的构造方法,生产环境用原构造方法创建真实客户端,测试环境则传入mock的客户端。

重构后的KeyVaultServiceImpl

import com.azure.security.keyvault.secrets.SecretClient;
import com.azure.security.keyvault.secrets.SecretClientBuilder;
import com.azure.security.keyvault.secrets.models.KeyVaultSecret;
import com.azure.identity.DefaultAzureCredentialBuilder;
import reactor.core.publisher.Mono;
import com.google.common.annotations.VisibleForTesting;

public class KeyVaultServiceImpl implements KeyVaultService {
    private final SecretClient secretClient;

    // 生产环境使用的构造方法,创建真实SecretClient
    public KeyVaultServiceImpl(String keyVaultUrl) {
        this.secretClient = new SecretClientBuilder()
                .vaultUrl(keyVaultUrl)
                .credential(new DefaultAzureCredentialBuilder().build())
                .buildClient();
    }

    // 测试专用构造方法,直接注入SecretClient(@VisibleForTesting用于标识测试用方法)
    @VisibleForTesting
    public KeyVaultServiceImpl(SecretClient secretClient) {
        this.secretClient = secretClient;
    }

    @Override
    public Mono<String> getSecret(String key) {
        KeyVaultSecret retrievedSecret = secretClient.getSecret(key);
        return Mono.just(retrievedSecret.getValue());
    }
}

单元测试代码(Mockito + StepVerifier)

import com.azure.security.keyvault.secrets.SecretClient;
import com.azure.security.keyvault.secrets.models.KeyVaultSecret;
import com.azure.security.keyvault.secrets.models.SecretProperties;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import reactor.test.StepVerifier;

import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class KeyVaultServiceImplTest {

    @Mock
    private SecretClient mockSecretClient;

    @Test
    void getSecret_returnsExpectedSecretValue() {
        // 测试数据准备
        String testKey = "db-password";
        String expectedSecretValue = "s3cr3tP@ssw0rd";
        KeyVaultSecret mockKeyVaultSecret = new KeyVaultSecret(new SecretProperties(testKey), expectedSecretValue);

        // 配置Mock行为:当调用getSecret(testKey)时返回预设的密钥对象
        when(mockSecretClient.getSecret(testKey)).thenReturn(mockKeyVaultSecret);

        // 创建测试用服务实例,传入mock的SecretClient
        KeyVaultServiceImpl serviceUnderTest = new KeyVaultServiceImpl(mockSecretClient);

        // 验证响应结果
        StepVerifier.create(serviceUnderTest.getSecret(testKey))
                .expectNext(expectedSecretValue)
                .verifyComplete();
    }
}

方案二:引入SecretClient工厂类

如果需要保留客户端创建的灵活性(比如不同环境有不同的客户端配置),可以引入工厂类封装SecretClient的创建逻辑,测试时mock工厂类来返回mock的客户端。

1. 定义工厂接口

import com.azure.security.keyvault.secrets.SecretClient;

public interface SecretClientFactory {
    SecretClient createSecretClient(String keyVaultUrl);
}

2. 实现生产环境工厂

import com.azure.security.keyvault.secrets.SecretClient;
import com.azure.security.keyvault.secrets.SecretClientBuilder;
import com.azure.identity.DefaultAzureCredentialBuilder;

public class DefaultSecretClientFactory implements SecretClientFactory {
    @Override
    public SecretClient createSecretClient(String keyVaultUrl) {
        return new SecretClientBuilder()
                .vaultUrl(keyVaultUrl)
                .credential(new DefaultAzureCredentialBuilder().build())
                .buildClient();
    }
}

3. 重构KeyVaultServiceImpl

import com.azure.security.keyvault.secrets.SecretClient;
import com.azure.security.keyvault.secrets.models.KeyVaultSecret;
import reactor.core.publisher.Mono;
import com.google.common.annotations.VisibleForTesting;

public class KeyVaultServiceImpl implements KeyVaultService {
    private final String keyVaultUrl;
    private final SecretClientFactory clientFactory;

    // 生产环境构造方法,使用默认工厂
    public KeyVaultServiceImpl(String keyVaultUrl) {
        this(keyVaultUrl, new DefaultSecretClientFactory());
    }

    // 测试专用构造方法,注入自定义工厂
    @VisibleForTesting
    public KeyVaultServiceImpl(String keyVaultUrl, SecretClientFactory clientFactory) {
        this.keyVaultUrl = keyVaultUrl;
        this.clientFactory = clientFactory;
    }

    @Override
    public Mono<String> getSecret(String key) {
        SecretClient secretClient = clientFactory.createSecretClient(keyVaultUrl);
        KeyVaultSecret retrievedSecret = secretClient.getSecret(key);
        return Mono.just(retrievedSecret.getValue());
    }
}

4. 单元测试代码

import com.azure.security.keyvault.secrets.SecretClient;
import com.azure.security.keyvault.secrets.models.KeyVaultSecret;
import com.azure.security.keyvault.secrets.models.SecretProperties;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import reactor.test.StepVerifier;

import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class KeyVaultServiceImplTest {

    @Mock
    private SecretClientFactory mockClientFactory;
    @Mock
    private SecretClient mockSecretClient;

    @Test
    void getSecret_returnsExpectedSecretValue() {
        // 测试数据准备
        String testVaultUrl = "https://test-vault.vault.azure.net/";
        String testKey = "api-key";
        String expectedSecretValue = "abc123xyz";
        KeyVaultSecret mockKeyVaultSecret = new KeyVaultSecret(new SecretProperties(testKey), expectedSecretValue);

        // 配置Mock行为:工厂返回mock客户端,客户端返回预设密钥
        when(mockClientFactory.createSecretClient(testVaultUrl)).thenReturn(mockSecretClient);
        when(mockSecretClient.getSecret(testKey)).thenReturn(mockKeyVaultSecret);

        // 创建测试用服务实例
        KeyVaultServiceImpl serviceUnderTest = new KeyVaultServiceImpl(testVaultUrl, mockClientFactory);

        // 验证响应
        StepVerifier.create(serviceUnderTest.getSecret(testKey))
                .expectNext(expectedSecretValue)
                .verifyComplete();
    }
}

内容的提问来源于stack exchange,提问作者Jonathan Hagen

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 23:10:45