如何在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
相关产品推荐
相关产品推荐

