使用MSTest与Mocking进行C#单元测试时遇未支持异常求助
Moq Mock设置抛出System.NotSupportedException错误的解决
错误信息
测试方法GUI_VIS.MsTests.LoginTest.TestEmptyIsAdminWindow抛出异常:
System.NotSupportedException: 不支持的表达式: x => x.GetAllEmailAsync("Pepa", "Heslo", new Worker() { })
不可重写的成员(此处为GenericController.GetAllEmailAsync)不能用于设置/验证表达式中。
堆栈跟踪
Guard.IsOverridable(MethodInfo method, Expression expression) 第109行 MethodExpectation.ctor(LambdaExpression expression, MethodInfo method, IReadOnlyList`1 arguments, Boolean exactGenericTypeArguments, Boolean skipMatcherInitialization, Boolean allowNonOverridable) 第87行 ExpressionExtensions.<Split>g__Split|5_0(Expression e, Expression& r, MethodExpectation& p, Boolean assignment, Boolean allowNonOverridableLastProperty) 第219行 ExpressionExtensions.Split(LambdaExpression expression, Boolean allowNonOverridableLastProperty) 第150行 Mock.SetupRecursive[TSetup](Mock mock, LambdaExpression expression, Func`4 setupLast, Boolean allowNonOverridableLastProperty) 第643行 Mock.Setup(Mock mock, LambdaExpression expression, Condition condition) 第498行 Mock`1.Setup[TResult](Expression`1 expression) 第452行 LoginTest.TestEmptyIsAdminWindow() 第48行 ThreadOperations.ExecuteWithAbortSafety(Action action)
测试代码
[TestMethod] public async Task TestEmptyIsAdminWindow() { Login login = new Login(); Mock<GenericController> gcMock = new Mock<GenericController>(); GenericController gc = new GenericController(); login.IdBoxGet().Text = "John"; login.PasswordBoxGet().Text = "Password"; ObservableCollection<Worker> workers = new ObservableCollection<Worker>() { new Worker { Name = "John", My_password = "Password", Role = "Boss" } }; gcMock.Setup(x => x.GetAllEmailAsync("John", "Heslo", new Worker() { })) .ReturnsAsync(workers); var workersFromMocking = await gc.GetAllEmailAsync("John", "Password", new Worker() { }); Assert.IsTrue(workersFromMocking[0].Name == "John"); }
问题分析与解决
1. 核心原因:目标方法不可重写
Moq仅支持mock可重写的成员,包括虚方法(virtual)、抽象方法,或是接口定义的方法。当前GenericController.GetAllEmailAsync是普通非虚方法,Moq无法对其进行Setup操作,这是报错的直接原因。
解决方式二选一:
- 修改
GenericController,将GetAllEmailAsync标记为virtual; - 定义接口
IGenericController,包含GetAllEmailAsync方法,让GenericController实现该接口,之后mockIGenericController而非具体类。
2. 未使用Mock实例
代码中创建了gcMock,但实际调用方法的是new GenericController()生成的真实实例,完全没有用到mock的配置。必须通过gcMock.Object获取mock实例并使用。
3. 参数不匹配
Setup中的参数是"John", "Heslo", new Worker(),但实际调用时用的是"John", "Password", new Worker(),参数不一致会导致mock的方法不触发。如果不需要严格匹配参数,可以用It.IsAny<string>()、It.IsAny<Worker>()等匹配器。
修改后的测试代码示例
[TestMethod] public async Task TestEmptyIsAdminWindow() { Login login = new Login(); // 假设已将GetAllEmailAsync改为虚方法 Mock<GenericController> gcMock = new Mock<GenericController>(); // 使用mock对象而非真实实例 GenericController gc = gcMock.Object; login.IdBoxGet().Text = "John"; login.PasswordBoxGet().Text = "Password"; ObservableCollection<Worker> workers = new ObservableCollection<Worker>() { new Worker { Name = "John", My_password = "Password", Role = "Boss" } }; // 修正参数,或使用匹配器兼容任意Worker实例 gcMock.Setup(x => x.GetAllEmailAsync("John", "Password", It.IsAny<Worker>())) .ReturnsAsync(workers); var workersFromMocking = await gc.GetAllEmailAsync("John", "Password", new Worker()); Assert.IsTrue(workersFromMocking[0].Name == "John"); }
内容的提问来源于stack exchange,提问作者NinjaSteve 1
相关产品推荐
相关产品推荐

