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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 13:03:10