如何用NSubstitute模拟继承抽象类并实现接口的Phone类?
我明白你的困扰——你想测试PhoneController,但它依赖的是具体的Phone类(而不是它的抽象基类Device或接口IPhone),直接用Substitute.For<Device, IPhone>()得到的对象类型不匹配,没法传给控制器的构造函数。下面分两种场景给你解决方案:
场景一:PhoneController仅依赖Phone的IPhone接口方法
方案1:调整依赖(推荐)
最佳实践是针对抽象编程,而不是具体类。你可以把PhoneController的依赖改成抽象组合,比如定义一个包含Device和IPhone能力的接口,或者直接注入两个抽象:
// 定义合并抽象(可选) public interface IPhoneDevice : Device, IPhone { } // 修改PhoneController的构造函数 public class PhoneController { private readonly IPhoneDevice _phone; public PhoneController(IPhoneDevice phone) { _phone = phone; } } // 测试代码 [TestMethod] public void TestMethod1() { var mockPhone = Substitute.For<IPhoneDevice>(); var controller = new PhoneController(mockPhone); // 验证调用逻辑 controller.MakeCall(); mockPhone.Received().MakeCall(); }
方案2:模拟具体Phone类(临时方案)
如果暂时无法调整抽象,NSubstitute可以模拟具体类,但要求需要拦截的方法是虚方法。先修改Phone类:
public class Phone : Device, IPhone { public virtual void MakeCall() { // 添加virtual关键字 throw new NotImplementedException(); } }
然后直接模拟Phone类完成测试:
[TestMethod] public void TestMethod1() { var mockPhone = Substitute.For<Phone>(); var controller = new PhoneController(mockPhone); // 执行测试并验证 controller.MakeCall(); mockPhone.Received().MakeCall(); }
场景二:PhoneController同时用到Device的抽象方法和IPhone的方法
这个场景里控制器既调用了Device的抽象GetStatus(),又调用了IPhone的MakeCall(),核心问题还是依赖具体类的类型不匹配。
方案1:调整依赖(推荐)
把PhoneController的依赖改成抽象基类Device,再转成IPhone使用(确保传入的Device实现了IPhone):
// 修改PhoneController public class PhoneController { private readonly Device _device; private readonly IPhone _phone; public PhoneController(Device device) { _device = device; _phone = (IPhone)device; } public string GetDeviceStatus() => _device.GetStatus(); public void MakeCall() => _phone.MakeCall(); } // 测试代码 [TestMethod] public void TestMethod1() { var mockDevice = Substitute.For<Device, IPhone>(); mockDevice.GetStatus().Returns("Online"); // 配置抽象方法返回值 var controller = new PhoneController(mockDevice); // 测试GetDeviceStatus var status = controller.GetDeviceStatus(); Assert.AreEqual("Online", status); // 测试MakeCall controller.MakeCall(); mockDevice.Received().MakeCall(); }
方案2:模拟具体Phone类(无需调整控制器)
如果必须保留控制器对Phone的依赖,把Phone中需要模拟的MakeCall设为虚方法,然后直接模拟:
// 修改Phone类 public class Phone : Device, IPhone { public virtual void MakeCall() { throw new NotImplementedException(); } public override string GetStatus() { throw new NotImplementedException(); } } // 测试代码 [TestMethod] public void TestMethod1() { var mockPhone = Substitute.For<Phone>(); mockPhone.GetStatus().Returns("Offline"); var controller = new PhoneController(mockPhone); var status = controller.GetDeviceStatus(); Assert.AreEqual("Offline", status); controller.MakeCall(); mockPhone.Received().MakeCall(); }
关键提醒
尽量避免让控制器依赖具体类,而是依赖抽象(接口或抽象基类),这样测试时模拟会更灵活,也符合SOLID原则。如果实在没法修改现有代码,就通过把需要模拟的方法改成虚方法来让NSubstitute可以拦截调用。
内容的提问来源于stack exchange,提问作者mkul
相关产品推荐
相关产品推荐

