如何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
相关产品推荐
相关产品推荐

