如何对基于Renci.SshNet的SFTP包装类进行单元测试?
SftpClient封装与单元测试问题
我封装了Renci.SshNet.Sftp.SftpClient实现了MySftpClient类,编写单元测试时调用Connect方法会尝试真实连接导致报错,无法断言该方法的调用次数。现咨询以下问题:
- 能否模拟基类方法?还是应采用组合而非继承,为SftpClient定义接口并注入到MySftpClient的构造函数中?
- 是否应为SftpClient使用.NET依赖注入,例如
services.AddScoped<ISftpClient, SftpClient>()?
我的包装类
public class MySftpClient: SftpClient, IMySftpClient { public MySftpClient(string host, int port, string username, string password) : base(new PasswordConnectionInfo(host, port, username, password)) { } public new void Connect() { base.Connect(); } }
单元测试代码
[TestMethod] public void Connect_Successful_Valid() { var mySftpClient = new MySftpClient("localhost", 22, "root", "password"); mySftpClient.Connect(); // 此处报错,因为代码尝试使用模拟连接信息连接真实服务器 // 无法断言Connect()方法被调用一次 }
可能的解决方案
public class MySftpClient: IMySftpClient { private ISftpClient sftpClient; public MySftpClient( ISftpClient sftpClient, string host, int port, string username, string password ) { // 实例化PasswordConnectionInfo... } public void Connect() { sftpClient.Connect(); } }
问题解答
1. 优先采用组合模式,不建议模拟基类方法
直接继承SftpClient后,无法直接模拟base.Connect()——因为SftpClient的Connect方法不是虚方法,主流模拟框架(如Moq)只能处理虚方法或接口方法。
你提出的组合方案是正确方向:为SftpClient定义抽象接口(如ISftpClient),让MySftpClient依赖该接口而非继承SftpClient。这样单元测试时可以轻松模拟接口实例,验证Connect调用次数,同时避免真实网络连接。
2. 建议通过.NET依赖注入管理SftpClient
是的,应该用依赖注入容器配置ISftpClient的实例,比如services.AddScoped<ISftpClient, SftpClientAdapter>()(根据业务场景选择Scoped/Transient/Singleton)。这么做的好处:
- 统一管理
SftpClient的生命周期,避免资源泄漏 - 测试环境中可直接替换为模拟实现,无需修改业务代码
- 符合依赖倒置原则,让
MySftpClient的依赖更清晰
优化后的实现示例
// 定义抽象接口,包含需要使用的SftpClient方法 public interface ISftpClient { void Connect(); void Disconnect(); // 按需添加UploadFile、DownloadFile等方法 } // 适配原生SftpClient的实现类 public class SftpClientAdapter : ISftpClient { private readonly Renci.SshNet.Sftp.SftpClient _innerClient; public SftpClientAdapter(string host, int port, string username, string password) { _innerClient = new Renci.SshNet.Sftp.SftpClient(new PasswordConnectionInfo(host, port, username, password)); } public void Connect() => _innerClient.Connect(); public void Disconnect() => _innerClient.Disconnect(); // 实现其他接口方法... } // 业务封装类 public class MySftpClient : IMySftpClient { private readonly ISftpClient _sftpClient; // 通过构造函数注入依赖 public MySftpClient(ISftpClient sftpClient) { _sftpClient = sftpClient; } public void Connect() { _sftpClient.Connect(); } }
单元测试示例(使用Moq)
[TestMethod] public void Connect_ShouldInvokeSftpClientConnectOnce() { // 创建ISftpClient的模拟实例 var mockSftp = new Mock<ISftpClient>(); var mySftpClient = new MySftpClient(mockSftp.Object); // 调用待测试方法 mySftpClient.Connect(); // 断言Connect方法被调用一次 mockSftp.Verify(client => client.Connect(), Times.Once); }
内容的提问来源于stack exchange,提问作者Mihai Socaciu
相关产品推荐
相关产品推荐

