如何用Moq验证AuthManager异常时CancelTransaction方法被调用?
第一个问题:"Non-override method cannot be verified" 错误解决
这个错误的根源是你最初大概率直接Mock了AuthManager具体类,而非IAuthManager接口。Moq对类的方法做拦截和验证时,要求方法必须标记为virtual,但对接口方法没有这个限制。
解决办法:
- 直接Mock
IAuthManager接口,而非具体的AuthManager类。只要你的业务代码依赖的是IAuthManager接口而非具体实现,Moq就能正常验证接口方法的调用,完全不需要给方法加virtual关键字。
第二个问题:测试失败(Expected invocation on the mock once, but was 0 times)
看你的测试代码,有三个核心问题导致验证失败:
1. Mock实例未注入被测对象
你创建了mockAuth,但实例化的是真实的AuthManager对象,完全没把mockAuth注入进去。真实的AuthManager调用的是自身的CancelTransaction方法,和你的Mock实例毫无关联,自然检测不到调用。
2. 方法名拼写不一致
你Setup的是CancelAuthorization方法,但Verify的是CancelTransaction方法,两者名称不匹配,就算真的调用了也会验证失败。
3. 测试逻辑的误区
如果你的AuthManager是自身实现IAuthManager接口,且AuthorizeTransaction抛出异常时调用的是自身的CancelTransaction方法,那么MockIAuthManager接口根本无法验证这个内部调用——因为你测试的是真实实例的方法,而非依赖注入的接口实例。
针对这个场景的修正方案:
方案A:调整代码依赖结构(推荐)
把CancelTransaction的逻辑抽离为独立依赖,或者让AuthManager依赖IAuthManager接口(比如用装饰器模式),这样就能通过注入Mock实例来验证调用。示例代码如下:
// 修改AuthManager构造函数,注入IAuthManager依赖 public class AuthManager : IAuthManager { private readonly IAuthManager _authDelegate; // 其他依赖... public AuthManager(IAuthManager authDelegate, /* 其他依赖 */) { _authDelegate = authDelegate; // 初始化其他依赖 } public async Task AuthorizeTransaction(AuthModel model) { try { // 授权逻辑... throw new Exception("授权失败"); } catch { await _authDelegate.CancelTransaction(new CancelTransactionModel()); } } public async Task CancelTransaction(CancelTransactionModel model) { // 取消逻辑... } }
对应的测试代码修改:
// Arrange var mockAuth = new Mock<IAuthManager>(); // 确保方法名一致 mockAuth.Setup(mock => mock.CancelTransaction(It.IsAny<CancelTransactionModel>())); // 将mock注入到AuthManager中 var auth = new AuthManager(mockAuth.Object, new FakeDataRepo(), fakeDataManager, fakeCardManager(), fakeSearchManager, fakeTokenManager, new fakeConnection(), fakeDataManager); var authModel = GenerateModel(/* 参数填充 */); // Act await auth.AuthorizeTransaction(authModel); // Assert(无需try-catch,异常已被被测方法内部处理) mockAuth.Verify(x => x.CancelTransaction(It.IsAny<CancelTransactionModel>()), Times.Once);
方案B:不修改代码结构,验证方法副作用
如果不想调整代码结构,可以验证CancelTransaction执行后的副作用——比如检查数据库中是否生成了取消记录、某个依赖方法是否被调用,而非直接验证方法本身的调用次数。例如,如果CancelTransaction会调用FakeDataRepo的RecordCancel方法,就验证这个方法的调用次数。
方案C:使用其他隔离工具(不推荐,除非必要)
如果一定要验证非virtual方法的调用,可以用微软的Fakes框架(仅Visual Studio Enterprise版支持)或者TypeMock等工具,它们能拦截非virtual方法,但这类工具学习成本高,不如Moq轻量。
内容的提问来源于stack exchange,提问作者Gagandeep Singh

