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

使用NSubstitute和Xunit模拟Azure存储时GetBlockBlobReference返回NULL

问题原因分析

1. GetCloudBlobContainer中blobClient.GetContainerReference返回null

  • 测试mock参数不匹配:业务代码里将容器名转为小写(containerName.ToLower()),但测试中传入的是大写开头的"Tenant",导致mock未触发,真实方法被执行。
  • 真实方法执行时,若连接字符串配置路径错误(比如AzureStorageConnectionStringKey未指向"Azurestorage:AzureStorageConnectionString"),或测试环境无实际Azure存储账户支撑,会引发后续逻辑异常,表现为返回null(注:GetContainerReference本身不会返回null,实际是连接字符串解析失败或依赖未正确初始化导致的连锁问题)。

2. Upload中blobContainer.GetBlockBlobReference返回null

  • 仅mock了CloudBlobContainer对象,但未配置GetBlockBlobReference方法的返回值,NSubstitute对未显式配置的引用类型方法默认返回null。

解决方案

步骤1:修复mock参数匹配问题

修改测试中GetCloudBlobContainer的mock参数为小写,匹配业务代码的处理逻辑:

[Fact]
public async Task Should_Upload_Assessment_Custom_Document()
{
    var container = Substitute.For<CloudBlobContainer>(new Uri("http://legalregtechquestiondocs/"));
    // 提前mock GetBlockBlobReference的返回值
    var blockBlob = Substitute.For<CloudBlockBlob>(new Uri("http://legalregtechquestiondocs/test-blob"));
    container.GetBlockBlobReference(Arg.Any<string>()).Returns(blockBlob);

    UsingDbContext(async context =>
    {
        // 使用小写容器名匹配业务代码的ToLower()处理
        _azureStorageHelper.GetCloudBlobContainer("tenant").Returns(container);
    });

    var _uploadDoc = await _assessmentCutsomDocumentAppService.Upload("xyz.doc", 1001);
    SaveChanges();

    Assert.NotNull(_uploadDoc);
    Assert.Equal("xyz.doc", _uploadDoc.DisplayFileName);
}

步骤2:配置GetBlockBlobReference的mock返回值

在创建CloudBlobContainer的mock后,添加方法配置,让它返回mock的CloudBlockBlob对象,避免返回null:

var blockBlob = Substitute.For<CloudBlockBlob>(new Uri("http://legalregtechquestiondocs/test-blob"));
// 支持任意文件名参数匹配
container.GetBlockBlobReference(Arg.Any<string>()).Returns(blockBlob);
// 若需匹配特定文件名,可改用:Arg.Is<string>(name => name.Contains("xyz.doc"))

步骤3:优化业务代码可测试性(可选但推荐)

  • 将GetCloudBlobContainer从静态方法改为实例方法,通过构造函数注入IConfiguration,避免直接依赖静态逻辑:
public interface IAzureStorageHelper
{
    Task<CloudBlobContainer> GetCloudBlobContainer(string containerName, bool isPrivate = true);
}

public class AzureStorageHelper : IAzureStorageHelper
{
    private readonly IConfiguration _configuration;
    private const string AzureStorageConnectionStringKey = "Azurestorage:AzureStorageConnectionString";

    public AzureStorageHelper(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    public async Task<CloudBlobContainer> GetCloudBlobContainer(string containerName, bool isPrivate = true)
    {
        string storageConnectionString = _configuration[AzureStorageConnectionStringKey];
        containerName = containerName.ToLower();
        var storageAccount = CloudStorageAccount.Parse(storageConnectionString);
        var blobClient = storageAccount.CreateCloudBlobClient();
        var container = blobClient.GetContainerReference(containerName);
        await container.CreateIfNotExistsAsync();

        if (isPrivate)
        {
            await container.SetPermissionsAsync(new BlobContainerPermissions
            {
                PublicAccess = BlobContainerPublicAccessType.Off
            });
        }
        else
        {
            await container.SetPermissionsAsync(new BlobContainerPermissions
            {
                PublicAccess = BlobContainerPublicAccessType.Blob
            });
        }

        return container;
    }
}

步骤4:验证连接字符串配置

确保AzureStorageConnectionStringKey的值为"Azurestorage:AzureStorageConnectionString",与appsettings中的嵌套配置路径完全匹配,避免无法读取连接字符串引发的异常。


内容的提问来源于stack exchange,提问作者San Jaisy

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 06:15:39