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

SSH.Net.Sftp不可重写属性无法模拟的单元测试问题

问题:无法模拟SSH.Net的SftpFileAttributes类进行单元测试

业务场景

单元测试时需要跳过部分代码逻辑,直接走到return IsFile;分支。其中client是基于SSH.Net SftpClient封装的可模拟类,SftpCheckPathExistence为自定义枚举。

业务代码

SftpFileAttributes attrs = client.GetAttributes(fileName);
if (attrs.IsDirectory)
{
    return SftpCheckPathExistence.IsDirectory;
}

return SftpCheckPathExistence.IsFile;

单元测试卡点

测试代码中需要模拟GetAttributes方法的返回值,但SftpFileAttributes类无法直接处理:

clientMock.Setup(f => f.GetAttributes(It.IsAny<string>())).Returns(???);

已尝试的方法及问题

  • 尝试1:不设置Mock

    SftpFileAttributes attrs = client.GetAttributes(fileName); // attrs为null
    if (attrs.IsDirectory) // 触发空引用异常
    

    问题:未Mock的方法返回null,导致空引用报错。

  • 尝试2:直接实例化SftpFileAttributes

    var sftpFileAttributes = new SftpFileAttributes(); // 无公共构造函数,报错
    sftpFileAttributes.IsDirectory = false; // 属性为私有setter,无法赋值
    
    clientMock.Setup(f => f.GetAttributes(It.IsAny<string>())).Returns(sftpFileAttributes);
    

    问题:类无公共构造函数,且IsDirectory属性无法直接修改。

  • 尝试3:用Moq模拟SftpFileAttributes

    var sftpFileAttributesMock = new Mock<SftpFileAttributes>();
    sftpFileAttributesMock.SetupGet(f => f.IsDirectory).Returns(false);
    // 报错:IsDirectory未标记为virtual,无法被Moq重写
    
    clientMock.Setup(f => f.GetAttributes(It.IsAny<string>())).Returns(sftpFileAttributes.Object);
    

    错误信息:

    System.NotSupportedException
      HResult=0x80131515
      Message=Unsupported expression: f => f.IsDirectory
    Non-overridable members (here: SftpFileAttributes.get_IsDirectory) may not be used in setup / verification expressions.
      Source=Moq
      StackTrace:
       at Moq.Guard.IsOverridable(MethodInfo method, Expression expression)
    

    问题:Moq仅支持模拟可重写(virtual)成员,而IsDirectory不满足该条件。

解决方案

方案1:通过反射创建并修改实例

利用反射绕过无公共构造函数和私有setter的限制:

// 反射获取非公共构造函数
var constructor = typeof(SftpFileAttributes).GetConstructor(
    BindingFlags.NonPublic | BindingFlags.Instance,
    null,
    new Type[0],
    null);
var attrs = (SftpFileAttributes)constructor.Invoke(null);

// 反射修改IsDirectory的私有setter
typeof(SftpFileAttributes).GetProperty("IsDirectory")
    .SetValue(attrs, false);

// 设置Mock返回值
clientMock.Setup(f => f.GetAttributes(It.IsAny<string>())).Returns(attrs);

方案2:封装自定义DTO避开SSH.Net类限制

在封装SftpClient时,将GetAttributes的返回值转换为自定义数据对象,彻底隔离第三方类:

// 自定义DTO
public class SftpItemInfo
{
    public bool IsDirectory { get; set; }
    // 按需添加其他属性
}

// 封装的Client方法
public SftpItemInfo GetItemInfo(string fileName)
{
    var attrs = _sftpClient.GetAttributes(fileName);
    return new SftpItemInfo
    {
        IsDirectory = attrs.IsDirectory
    };
}

此时单元测试可直接Mock返回自定义的SftpItemInfo,无需处理SSH.Net的类限制。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 16:33:33