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

Azure File Share CreateAsync方法单元测试Mock配置问题排查

Azure文件共享创建方法的单元测试Mock配置错误分析

原业务方法代码

public async Task<ShareClient> CreateFileShare(string shareName)
{
    if (!ShareNameValidator.IsValid(shareName))
    {
        throw new ArgumentException("Invalid share name.");
    }

    ShareClient shareFileClient = _shareServiceClient.GetShareClient(shareName.ToLowerInvariant());
    if (await shareFileClient.ExistsAsync())
    {
        throw new ShareExistsException("Share already exists.");
    }
    await shareFileClient.CreateAsync();

    return shareFileClient;
}

单元测试情况

针对上述方法编写了3个Xunit单元测试:

public async Task CreateFileShare_ShouldThrowException_WhenShareAlreadyExists()
public async Task CreateFileShare_ShouldThrowException_WhenInvalidShareName()
public async Task CreateFileShare_ShouldCreateShare_WhenShareDoesNotExist()

前两个测试已通过,但第三个测试始终失败,以下是该测试的两种实现:

第一种实现

[Fact]
public async Task CreateFileShare_ShouldCreateShare_WhenShareDoesNotExist()
{
    // Arrange
    var responseMock = new Mock<Response<bool>>();
    string validShareName = "valid-share-name";
    responseMock.Setup(x => x.Value).Returns(false);
    _shareClientMock.Setup(x => x.ExistsAsync(It.IsAny<CancellationToken>())).Returns(Task.FromResult(responseMock.Object));
    _shareServiceClientMock.Setup(x => x.GetShareClient(validShareName)).Returns(_shareClientMock.Object);

    // Act
    var createdShareClient = await _fileStorageHelper.CreateFileShare(validShareName);

    // Assert
    createdShareClient.Should().Be(_shareClientMock.Object);
    _shareClientMock.Verify(x => x.CreateAsync(It.IsAny<ShareCreateOptions>(), It.IsAny<CancellationToken>()), Times.Once);
}

第二种实现

[Fact]
public async Task CreateFileShare_ShouldCreateShare_WhenShareDoesNotExist()
{
    // Arrange
    string validShareName = "validsharename";
    var responseMock = new Mock<Response<bool>>();
    var shareInfoMock = new Mock<ShareInfo>();
    var createResponseMock = new Mock<Response<ShareInfo>>();
    ShareCreateOptions createOptions = new ShareCreateOptions
    {
        QuotaInGB = 100,
    };

    shareInfoMock.DefaultValue = DefaultValue.Mock;
    responseMock.Setup(x => x.Value).Returns(false);
    createResponseMock.Setup(x => x.Value).Returns(shareInfoMock.Object);
    _shareClientMock.Setup(x => x.ExistsAsync(It.IsAny<CancellationToken>())).Returns(Task.FromResult(responseMock.Object));
    _shareClientMock.Setup(x => x.CreateAsync(createOptions, It.IsAny<CancellationToken>())).Returns(Task.FromResult(createResponseMock.Object));
    _shareServiceClientMock.Setup(x => x.GetShareClient(validShareName)).Returns(_shareClientMock.Object);

    // Act
    var createdShareClient = await _fileStorageHelper.CreateFileShare(validShareName);

    // Assert
    createdShareClient.Should().Be(_shareClientMock.Object);
    _shareClientMock.Verify(x => x.ExistsAsync(It.IsAny<CancellationToken>()), Times.Once());
    _shareClientMock.Verify(x => x.CreateAsync(It.IsAny<ShareCreateOptions>(), It.IsAny<CancellationToken>()), Times.Once());
}        

错误信息

Message: 
Moq.MockException : 
Expected invocation on the mock once, but was 0 times: x => 
x.CreateAsync(It.IsAny<ShareCreateOptions>(), 
It.IsAny<CancellationToken>())
Performed invocations:

Mock<ShareClient:1> (x):

  ShareClient.ExistsAsync(CancellationToken)  => Mock<Response<bool>:1>
  ShareClient.CreateAsync(null, null, CancellationToken)

Mock<Response<bool>:1>:

  Response<bool>.Value

问题原因与修复方案

1. GetShareClient参数大小写不匹配

原方法中调用_shareServiceClient.GetShareClient(shareName.ToLowerInvariant()),会将传入的shareName转为小写后再调用接口,但测试代码中Setup时使用的是原始的validShareName(未转小写)。当validShareName包含大写字母时,Mock无法匹配该调用,会返回默认的ShareClient实例(而非预设的_shareClientMock),导致后续验证逻辑失效。

修复:将Setup中的参数改为小写版本:

_shareServiceClientMock.Setup(x => x.GetShareClient(validShareName.ToLowerInvariant()))
                       .Returns(_shareClientMock.Object);

2. CreateAsync重载不匹配

原方法调用的是无参数的CreateAsync(),对应Azure SDK中的实际重载是三参数版本:

Task<Response<ShareInfo>> CreateAsync(ShareCreateOptions? options = null, ShareCreateHeaders? headers = null, CancellationToken cancellationToken = default);

无参数调用会默认传入null、null、CancellationToken.None。但测试代码中Verify的是两参数版本的CreateAsync(ShareCreateOptions, CancellationToken),Moq认为该重载从未被调用,因此抛出异常。

修复:调整Verify语句,匹配实际调用的三参数重载:

// 匹配任意参数的三参数重载
_shareClientMock.Verify(x => x.CreateAsync(It.IsAny<ShareCreateOptions>(), 
                                           It.IsAny<ShareCreateHeaders>(), 
                                           It.IsAny<CancellationToken>()), 
                        Times.Once);

// 或者更精准匹配默认传入的null参数
_shareClientMock.Verify(x => x.CreateAsync(null, null, It.IsAny<CancellationToken>()), 
                        Times.Once);

同时,第二种测试实现中Setup的CreateAsync重载也需要改为三参数版本:

_shareClientMock.Setup(x => x.CreateAsync(null, null, It.IsAny<CancellationToken>()))
               .Returns(Task.FromResult(createResponseMock.Object));

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 20:30:56