如何用Moq模拟含Func类型属性的接口并在测试中调用?
正确用Moq模拟委托类型属性的方法
你之前误用了Moq的SetupAdd()——这个方法是专门给事件设计的,但QuickNotification是个委托类型的普通属性,底层实现和事件完全不同,所以会抛出类型转换异常。下面是两种可行的解决方案:
方案1:初始化属性为可累加的空委托,直接调用绑定后的逻辑
核心思路:给QuickNotification属性的get方法返回一个空委托(避免Subject构造时+=触发空引用异常),等被测类完成绑定后,直接取出属性值调用,就能触发NotificationSink并拿到返回结果。
测试代码示例:
// 1. 创建Mock并初始化委托属性 var resourceMock = new Mock<IResource>(); // 初始返回空委托,确保Subject构造时+=不会报错 resourceMock.SetupGet(r => r.QuickNotification) .Returns((string msg) => default(Response)); // 2. 实例化被测类,自动绑定NotificationSink到属性 var subject = new Subject(resourceMock.Object); // 3. 取出绑定后的完整委托 var boundDelegate = resourceMock.Object.QuickNotification; // 4. 调用委托触发处理逻辑,获取返回结果 var testMessage = "test alert"; Response result = boundDelegate(testMessage); // 5. 自定义断言验证结果 // Assert.That(result, Is.Not.Null); // 比如检查Response的状态码、内容等
方案2:用SetupSet捕获绑定的委托
如果需要精确控制委托的绑定过程,可以设置属性的set回调,把Subject绑定的委托存下来再调用:
Func<string, Response> capturedDelegate = null; var resourceMock = new Mock<IResource>(); // 捕获属性被赋值时的委托 resourceMock.SetupSet(r => r.QuickNotification = It.IsAny<Func<string, Response>>()) .Callback<Func<string, Response>>(func => capturedDelegate = func); // 必须同时SetupGet,因为Subject构造时会先get原有委托再+=(本质是get后set新委托) resourceMock.SetupGet(r => r.QuickNotification) .Returns(capturedDelegate); // 实例化被测类 var subject = new Subject(resourceMock.Object); // 调用捕获到的委托 var testMessage = "test alert"; Response result = capturedDelegate(testMessage); // 验证结果 // Assert.That(result, Is.Not.Null);
关键说明
- 委托属性的
+=本质是先get原有委托,再通过Delegate.Combine合并新方法,最后set回属性,属于普通的属性读写操作,和事件的订阅机制完全不同,因此不能用事件专用的SetupAdd()。 - 必须确保属性的
get方法初始返回非null委托,否则Subject构造时+=会触发NullReferenceException。
内容的提问来源于stack exchange,提问作者rabindra
相关产品推荐
相关产品推荐

