XUnit单元测试Mock实例方法报错问题咨询
问题分析与解决方案
报错原因
你遇到的System.NotSupportedException核心问题有两个:
- Moq无法Mock非可重写成员:你要Mock的
NPFaxAsEmailBL.SendEmail是普通的public方法,没有标记virtual、abstract,也没提取成接口方法。Moq靠创建类的动态代理来注入Mock逻辑,非可重写的方法没办法被代理重写,所以直接抛出异常。 - 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
相关产品推荐
相关产品推荐

