如何Mock运行时动态创建的对象?单元测试Mock Connection类
动态创建对象的Mock方案及代码优化建议
一、Mock动态创建的DeviceConnection方案
你当前代码直接在方法内new DeviceConnection和new TcpClient,导致无法直接Mock这类动态创建的对象。核心解决思路是把对象创建逻辑抽象出来,通过依赖注入提供,这样单元测试时就能替换成Mock实现。
步骤1:抽象对象创建工厂
先定义一个工厂接口,专门负责创建IDeviceConnection实例:
public interface IDeviceConnectionFactory { IDeviceConnection Create(IPEndPoint ipEndPoint, CancellationToken stoppingToken); }
再实现生产环境用的工厂类:
public class DeviceConnectionFactory : IDeviceConnectionFactory { public IDeviceConnection Create(IPEndPoint ipEndPoint, CancellationToken stoppingToken) { return new DeviceConnection(ipEndPoint, new TcpClient(), stoppingToken); } }
步骤2:重构原代码,注入工厂
把工厂通过构造函数注入到你的类中,替换掉直接new的硬编码逻辑:
private readonly Dictionary<IPEndPoint, IDeviceConnection> _connections = new Dictionary<IPEndPoint, IDeviceConnection>(); private readonly IDeviceConnectionFactory _connectionFactory; // 构造函数注入工厂 public YourClassName(IDeviceConnectionFactory connectionFactory) { _connectionFactory = connectionFactory; } private async Task DiscoveryTask(CancellationToken stoppingToken) { try { while (!stoppingToken.IsCancellationRequested) { // 假设ipEndPoint是你的设备发现逻辑获取到的目标地址 if (!_connections.ContainsKey(ipEndPoint)) { var connection = _connectionFactory.Create(ipEndPoint, stoppingToken); _connections.Add(ipEndPoint, connection); connection.StartConnectionTask(); } // 加个延迟避免循环空转占用CPU await Task.Delay(1000, stoppingToken); } } catch (Exception ex) { // 至少加日志记录,别吞掉异常 // _logger.LogError(ex, "设备发现任务执行出错"); } }
步骤3:单元测试中Mock工厂与连接
以Moq框架为例,测试时可以这么写:
[Test] public async Task DiscoveryTask_发现新设备时_添加Mock连接并启动() { // 准备测试数据 var mockFactory = new Mock<IDeviceConnectionFactory>(); var mockConnection = new Mock<IDeviceConnection>(); var testIp = new IPEndPoint(IPAddress.Parse("192.168.1.1"), 8080); // 配置Mock工厂返回Mock连接 mockFactory.Setup(f => f.Create(testIp, It.IsAny<CancellationToken>())) .Returns(mockConnection.Object); var yourClass = new YourClassName(mockFactory.Object); var cts = new CancellationTokenSource(); // 执行测试:让任务跑一小段时间后取消 cts.CancelAfter(100); await yourClass.DiscoveryTask(cts.Token); // 验证逻辑是否符合预期 mockFactory.Verify(f => f.Create(testIp, It.IsAny<CancellationToken>()), Times.Once); mockConnection.Verify(c => c.StartConnectionTask(), Times.Once); }
二、代码修改建议
- 别吞掉所有异常:当前catch块是空的,生产环境会导致异常被隐藏,问题根本没法排查,至少要加日志记录异常详情。
- 加连接存在性检查:原代码直接调用
_connections.Add,如果同一个ipEndPoint重复被发现,会抛出ArgumentException,建议先判断!_connections.ContainsKey(ipEndPoint)再添加。 - 添加循环延迟:无延迟的while循环会疯狂占用CPU,加
await Task.Delay(xxx, stoppingToken)能大幅降低资源消耗。 - 进一步解耦TcpClient:如果需要Mock
TcpClient,可以给它套个ITcpClient接口,或者把TcpClient的创建也放到工厂里,避免硬依赖具体实现。 - 考虑线程安全:如果
_connections会被多个线程读写,要么加lock(_connections),要么换成线程安全的ConcurrentDictionary<IPEndPoint, IDeviceConnection>。
内容的提问来源于stack exchange,提问作者Hamza
相关产品推荐
相关产品推荐

