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

如何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:如果需要MockTcpClient,可以给它套个ITcpClient接口,或者把TcpClient的创建也放到工厂里,避免硬依赖具体实现。
  • 考虑线程安全:如果_connections会被多个线程读写,要么加lock(_connections),要么换成线程安全的ConcurrentDictionary<IPEndPoint, IDeviceConnection>。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 04:06:18