Azure管道中CosmosClientFactory单元测试的DefaultAzureCredential异常问题
问题解决:Azure Pipeline中CosmosClientFactory单元测试的DefaultAzureCredential错误
问题原因
在Azure Pipeline这类CI/CD环境中,DefaultAzureCredential会依次尝试AzurePowerShellCredential、AzureDeveloperCliCredential、EnvironmentCredential、ManagedIdentityCredential等凭据提供者,但测试环境未配置这些有效凭据,导致抛出凭据不可用的错误。而单元测试的核心是验证CosmosClientFactory的逻辑,无需真的获取Azure服务的有效令牌。
修复方案
1. 用模拟对象替代真实的DefaultAzureCredential
使用Moq等模拟框架创建TokenCredential的模拟实例,跳过真实的凭据获取流程,专注于验证工厂逻辑。
2. 调整测试代码
修改GetCosmosClient_ReturnsExpectedClient测试方法,移除真实的DefaultAzureCredential调用,改用模拟对象:
using Azure.Identity; using Azure.Core; using Microsoft.Azure.Cosmos; using Microsoft.DigitalWorkplace.GCS.CommonAPI.DbClient.Factories; using Microsoft.DigitalWorkplace.GCS.CommonAPI.DbClient.Models.Configuration; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Xunit; using Moq; namespace Microsoft.DigitalWorkplace.GcsApi.CommonApi.DbClient.Tests.Factories { public sealed class CosmosClientFactoryTests { #region Constructor Tests [Fact] public void Constructor_ThrowsNullExceptionForNullParam() { ArgumentNullException exception = Assert.Throws<ArgumentNullException>(() => new CosmosClientFactory(logger: null)); Assert.Equal("Value cannot be null. (Parameter 'logger')", exception.Message); } [Fact] public void Constructor_CreatesInstanceForNonNullLogger() { CosmosClientFactory cosmosClientFactory = CreateCosmosClientFactory(); Assert.NotNull(cosmosClientFactory); } #endregion #region GetCosmosClient Tests [Fact] public void GetCosmosClient_ReturnsExpectedClient() { CosmosClientFactory cosmosClientFactory = CreateCosmosClientFactory(); CosmosDbClientSettings clientSettings = new CosmosDbClientSettings { CosmosDBEndpoint = new Uri("https://localhost:8081/").ToString(), ManagedIdentityClientId = "00000000-0000-0000-0000-000000000000", CommonAPIContainerSettings = new ContainerSettings { ContainerName = "TestContainer", DatabaseName = "TestDb", }, }; // 模拟TokenCredential,返回假令牌 var mockCredential = new Mock<TokenCredential>(); mockCredential.Setup(c => c.GetTokenAsync(It.IsAny<TokenRequestContext>(), It.IsAny<CancellationToken>())) .ReturnsAsync(new AccessToken("dummy-token", DateTimeOffset.UtcNow.AddHours(1))); // 传入模拟凭据获取CosmosClient CosmosClient actualClient = cosmosClientFactory.GetCosmosClient(clientSettings, mockCredential.Object); Assert.NotNull(actualClient); Assert.Equal(clientSettings.CosmosDBEndpoint, actualClient.Endpoint.ToString()); clientSettings = clientSettings with { CosmosDBEndpoint = new Uri("https://testhost:8081/").ToString() }; actualClient = cosmosClientFactory.GetCosmosClient(clientSettings, mockCredential.Object); Assert.NotNull(actualClient); Assert.Equal(clientSettings.CosmosDBEndpoint, actualClient.Endpoint.ToString()); } #endregion #region Helper Methods private static CosmosClientFactory CreateCosmosClientFactory() { using NullLoggerFactory loggerFactory = new NullLoggerFactory(); ILogger<CosmosClientFactory> mockLogger = loggerFactory.CreateLogger<CosmosClientFactory>(); return new CosmosClientFactory(logger: mockLogger); } #endregion }
3. 重构CosmosClientFactory(按需调整)
如果CosmosClientFactory内部直接实例化DefaultAzureCredential,需重构以支持依赖注入TokenCredential,方便测试时传入模拟对象:
public class CosmosClientFactory { private readonly ILogger<CosmosClientFactory> _logger; private readonly TokenCredential _tokenCredential; // 重载构造函数,允许注入TokenCredential public CosmosClientFactory(ILogger<CosmosClientFactory> logger, TokenCredential tokenCredential = null) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _tokenCredential = tokenCredential; } public CosmosClient GetCosmosClient(CosmosDbClientSettings settings) { var credential = _tokenCredential ?? new DefaultAzureCredential(new DefaultAzureCredentialOptions { ManagedIdentityClientId = settings.ManagedIdentityClientId }); return new CosmosClient(settings.CosmosDBEndpoint, credential); } }
额外建议
- 单元测试应聚焦业务逻辑,避免依赖外部服务或真实凭据获取流程。
- 在Azure Pipeline中运行测试时,确保所有外部依赖都被模拟或存根,不访问真实Azure资源。
内容的提问来源于stack exchange,提问作者Nagarjun
相关产品推荐
相关产品推荐

