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

XUnit单元测试Mock实例方法报错问题咨询

问题分析与解决方案

报错原因

你遇到的System.NotSupportedException核心问题有两个:

  1. Moq无法Mock非可重写成员:你要Mock的NPFaxAsEmailBL.SendEmail是普通的public方法,没有标记virtual、abstract,也没提取成接口方法。Moq靠创建类的动态代理来注入Mock逻辑,非可重写的方法没办法被代理重写,所以直接抛出异常。
  2. Mock对象与实际调用对象不匹配:你代码里创建了_mockNPFaxAsEmailBL并设置了Mock规则,但实际测试时调用的是_nPFaxAsEmailBL(直接new出来的真实实例),这俩是完全独立的对象,Mock的设置根本不会作用到真实实例上——就算解决第一个问题,测试逻辑也不会生效。

解决方案

这里推荐两种方案,优先选第一种(符合代码设计原则):

方案1:重构代码,遵循单一职责原则(最优解)

你的NPFaxAsEmailBL类已经依赖了IEmailSender,但却自己实现了SendEmail方法,这不符合单一职责——业务逻辑类不该负责具体的邮件发送实现。正确的做法是把SendEmail的逻辑移到IEmailSender的实现中,让BL类只调用依赖的邮件发送服务。

修改生产代码:

public class NPFaxAsEmailBL : INPFaxEmailBL {
    private IEmailSender _emailSender;
    public NPFaxAsEmailBL(IEmailSender emailSender) {
        _emailSender = emailSender;
    }
    public bool SendFaxAsEmail(FaxMailDTO faxMailDTO) {
        bool isEmailSuccessfully = false;
        bool validationCompleted = CompleteValidation(faxMailDTO);
        if (validationCompleted) {
            Log.Information($"{faxMailDTO?.NP}: FaxAsMailRequest Request validation Completed.");
            // 省略DTO赋值逻辑...
            // 替换为调用依赖的IEmailSender方法
            isEmailSuccessfully = _emailSender.SendEmail(AppSettingsHelper.RightFaxSMTPServer, faxMailDTO);
        }
        return isEmailSuccessfully;
    }
    // 移除原本的SendEmail方法,把逻辑放到IEmailSender的实现类中
}

修改测试代码:

public class NPFaxAsEmailTest {
    private NPFaxAsEmailBL _nPFaxAsEmailBL;
    private Mock<IEmailSender> _emailSenderMock;

    public NPFaxAsEmailTest() {
        _emailSenderMock = new Mock<IEmailSender>();
        // 直接使用真实的BL实例,依赖用Mock的IEmailSender
        _nPFaxAsEmailBL = new NPFaxAsEmailBL(_emailSenderMock.Object);
    }

    [Theory]
    [MemberData(nameof(GetFaxAsMailDTO))]
    public void SendFaxAsEmailTest_ReturnsTrue(FaxMailDTO faxMailDTO) {
        string sMTPServerUrl = AppSettingsHelper.RightFaxSMTPServer;
        // Mock IEmailSender的SendEmail方法,匹配任意FaxMailDTO或者精确匹配都可以
        _emailSenderMock.Setup(x => x.SendEmail(sMTPServerUrl, It.IsAny<FaxMailDTO>())).Returns(true);
        
        var result = _nPFaxAsEmailBL.SendFaxAsEmail(faxMailDTO);
        
        Assert.True(result);
        // 还可以验证SendEmail是否被调用
        _emailSenderMock.Verify(x => x.SendEmail(sMTPServerUrl, faxMailDTO), Times.Once);
    }
}

方案2:临时方案——将SendEmail改为虚方法(不推荐)

如果暂时无法重构生产代码,可以把SendEmail标记为virtual,让Moq能够重写它。但这种方法会污染生产代码,只为了测试需求修改,不符合最佳实践。

修改生产代码:

public class NPFaxAsEmailBL : INPFaxEmailBL {
    // ...其他代码不变
    // 给SendEmail添加virtual关键字
    public virtual bool SendEmail(string sMTPServerUrl, FaxMailDTO faxAsMailRequest) {
        // 原有逻辑不变
    }
}

修改测试代码:

public class NPFaxAsEmailTest {
    private Mock<NPFaxAsEmailBL> _mockNPFaxAsEmailBL;
    private Mock<IEmailSender> _emailSenderMock;

    public NPFaxAsEmailTest() {
        _emailSenderMock = new Mock<IEmailSender>();
        // 创建BL的Mock实例,传入依赖的IEmailSender Mock
        _mockNPFaxAsEmailBL = new Mock<NPFaxAsEmailBL>(_emailSenderMock.Object);
        // 需要设置CompleteValidation为返回true,否则不会走到SendEmail逻辑
        _mockNPFaxAsEmailBL.Setup(x => x.CompleteValidation(It.IsAny<FaxMailDTO>())).Returns(true);
    }

    [Theory]
    [MemberData(nameof(GetFaxAsMailDTO))]
    public void SendFaxAsEmailTest_ReturnsTrue(FaxMailDTO faxMailDTO) {
        string sMTPServerUrl = AppSettingsHelper.RightFaxSMTPServer;
        // 现在可以Mockvirtual的SendEmail方法了
        _mockNPFaxAsEmailBL.Setup(x => x.SendEmail(sMTPServerUrl, It.IsAny<FaxMailDTO>())).Returns(true);
        
        // 调用Mock实例的Object属性对应的方法
        var result = _mockNPFaxAsEmailBL.Object.SendFaxAsEmail(faxMailDTO);
        
        Assert.True(result);
    }
}

注意:这种方案下,你还需要MockCompleteValidation方法让它返回true,否则测试逻辑不会走到SendEmail的分支。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 10:47:26