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

如何用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 07:01:37