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

如何Mock C#函数内部创建的对象?单元测试技术问询

问题

我想要为C#应用编写单元测试,部分函数会在内部创建对象并对其执行检查。为保证单元测试的独立性,需将函数与对象依赖解耦。请问能否Mock函数内部创建的对象?

我当前要测试DeviceManager类的ConnectWirelessDeviceToSystem方法,但该方法依赖的WirelessDevice对象需要Mock。尝试使用Moq框架却未能成功实现。

相关代码
public struct Device
{
    public WirelessDevice WirelessDevice { get; set; }
    public ushort unitId { get; set; }
}

public class WirelessDevice
{
    public bool HasSerialNumber { get; set; }
    public ushort UnitId { get; set; }
    public string SerialNumber { get; set; }

    public WirelessDevice(ushort unitId, string serialNumber)
    {
        this.UnitId = unitId;

        if (!string.IsNullOrEmpty(serialNumber))
        {
            HasSerialNumber = true;
            this.SerialNumber = serialNumber;
        }
    }
}

public class DeviceManager
{
    private readonly Dictionary<uint, Device> _devices = new Dictionary<uint, Device>();
    
    public async Task ConnectWirelessDeviceToSystem(ushort unitId)
    {
        WirelessDevice wirelessDevice = _devices.FirstOrDefault(x => x.Key == unitId).Value.WirelessDevice;
        if (wirelessDevice == null)
        {
            return;
        }

        if (wirelessDevice.HasSerialNumber)
        {
            Console.WriteLine("Device has serial number {0}", wirelessDevice.SerialNumber);
        }
        else
        {
            Console.WriteLine("Device does not have serial number, can not connect to system");
        }
    }

    public bool Add(ushort unitId, string serialNumber)
    {
        WirelessDevice wirelessDevice = new WirelessDevice(
           unitId,
           serialNumber);

        _devices.Add(unitId, new Device()
        {
            WirelessDevice = wirelessDevice,
            unitId = unitId,
        });

        return true;
    }
}
解决方案

直接Mock函数内部通过new关键字创建的具体对象是做不到的,因为Moq这类框架需要依赖**抽象(接口或虚成员)**才能替换实现。要实现解耦,需要先重构代码,让WirelessDevice的创建逻辑可替换:

1. 抽象无线设备接口

先定义IWirelessDevice接口,包含业务需要的属性:

public interface IWirelessDevice
{
    bool HasSerialNumber { get; }
    ushort UnitId { get; }
    string SerialNumber { get; }
}

让WirelessDevice实现该接口:

public class WirelessDevice : IWirelessDevice
{
    public bool HasSerialNumber { get; set; }
    public ushort UnitId { get; set; }
    public string SerialNumber { get; set; }

    public WirelessDevice(ushort unitId, string serialNumber)
    {
        this.UnitId = unitId;

        if (!string.IsNullOrEmpty(serialNumber))
        {
            HasSerialNumber = true;
            this.SerialNumber = serialNumber;
        }
    }
}

2. 修改依赖类型

把Device结构体和DeviceManager中的WirelessDevice类型替换为IWirelessDevice:

public struct Device
{
    public IWirelessDevice WirelessDevice { get; set; }
    public ushort unitId { get; set; }
}

public class DeviceManager
{
    private readonly Dictionary<uint, Device> _devices = new Dictionary<uint, Device>();
    private readonly Func<ushort, string, IWirelessDevice> _wirelessDeviceFactory;

    // 通过构造函数注入工厂方法,默认使用真实的WirelessDevice构造逻辑
    public DeviceManager(Func<ushort, string, IWirelessDevice> wirelessDeviceFactory = null)
    {
        _wirelessDeviceFactory = wirelessDeviceFactory ?? ((id, sn) => new WirelessDevice(id, sn));
    }

    public async Task ConnectWirelessDeviceToSystem(ushort unitId)
    {
        IWirelessDevice wirelessDevice = _devices.FirstOrDefault(x => x.Key == unitId).Value.WirelessDevice;
        if (wirelessDevice == null)
        {
            return;
        }

        if (wirelessDevice.HasSerialNumber)
        {
            Console.WriteLine("Device has serial number {0}", wirelessDevice.SerialNumber);
        }
        else
        {
            Console.WriteLine("Device does not have serial number, can not connect to system");
        }
    }

    public bool Add(ushort unitId, string serialNumber)
    {
        // 使用工厂创建设备,而不是直接new
        IWirelessDevice wirelessDevice = _wirelessDeviceFactory(unitId, serialNumber);

        _devices.Add(unitId, new Device()
        {
            WirelessDevice = wirelessDevice,
            unitId = unitId,
        });

        return true;
    }
}

3. 编写单元测试

现在可以用Moq创建IWirelessDevice的Mock实例,注入到DeviceManager中测试不同场景:

using Moq;
using Xunit;

public class DeviceManagerTests
{
    [Fact]
    public async Task ConnectWirelessDeviceToSystem_WithSerialNumber_VerifiesSerialCheck()
    {
        // 准备Mock的无线设备
        var mockDevice = new Mock<IWirelessDevice>();
        mockDevice.Setup(d => d.HasSerialNumber).Returns(true);
        mockDevice.Setup(d => d.SerialNumber).Returns("SN12345");

        // 创建DeviceManager,注入返回Mock的工厂
        var manager = new DeviceManager((id, sn) => mockDevice.Object);
        manager.Add(1, "SN12345");

        // 执行测试方法
        await manager.ConnectWirelessDeviceToSystem(1);

        // 验证Mock的属性被正确访问
        mockDevice.Verify(d => d.HasSerialNumber, Times.Once);
        mockDevice.Verify(d => d.SerialNumber, Times.Once);
    }

    [Fact]
    public async Task ConnectWirelessDeviceToSystem_WithoutSerialNumber_VerifiesNoSerialCheck()
    {
        var mockDevice = new Mock<IWirelessDevice>();
        mockDevice.Setup(d => d.HasSerialNumber).Returns(false);

        var manager = new DeviceManager((id, sn) => mockDevice.Object);
        manager.Add(2, null);

        await manager.ConnectWirelessDeviceToSystem(2);

        mockDevice.Verify(d => d.HasSerialNumber, Times.Once);
    }
}

关键说明

  • 核心思路是依赖抽象而非具体实现,通过工厂方法或依赖注入把对象创建逻辑从业务代码中剥离,让测试时可以替换为Mock实例。
  • 如果不想改动太多代码,也可以用隔离框架(比如Microsoft Fakes)拦截new操作,但这种方式维护性较差,优先推荐基于接口的重构方案。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 22:37:06