C#中使用MOQ框架Mock Queue对象报错的原因及解决方法
问题:使用Moq模拟Queue时抛出NotSupportedException异常
尝试用Moq框架Mock Queue<ISampleData> 对象时,出现以下异常:
System.NotSupportedException: 'Unsupported expression: x => x.Enqueue(It.IsAny()) Non-overridable members (here: Queue.Enqueue) may not be used in setup / verification expressions.'
业务代码
public class ISampleData { int ID { get; set; } double Data { get; set; } } public class SampleData: ISampleData { public int ID { get; set; } public double Data { get; set; } } public class DataManager { private Queue<ISampleData> dataQueue; public DataManager(Queue<ISampleData> dataQueue) { this.dataQueue = dataQueue; } public bool InsertData(ISampleData sampleData) { bool flag = false; try { this.dataQueue.Enqueue(sampleData); flag = true; } catch(Exception ex) { flag = false; } return flag; } }
测试代码
/// <summary> /// Testing Enqueue method with exception. /// </summary> [Fact] public void Test_EnqueueMethod_With_Exception() { // Arrange Mock<Queue<ISampleData>> mockQueue = new Mock<Queue<ISampleData>>(); DataManager dataManager = new DataManager(mockQueue.Object); mockQueue.Setup(x => x.Enqueue(It.IsAny<ISampleData>())).Throws(new Exception()); // Act var actual = dataManager.InsertData(new SampleData() { ID = 1001, Data = 20.9876 }); // Assert Assert.False(actual); }
报错原因
- Moq核心限制:Moq的工作逻辑是创建目标类型的派生类,通过重写方法实现Mock。但
Queue<T>是密封类(sealed),且Enqueue方法是非虚方法,无法被重写,因此Moq无法对该方法进行Setup或验证操作。 - 业务代码小问题:
ISampleData被定义为class而非interface,导致SampleData并未正确实现其成员(ISampleData的属性为私有,SampleData的为公共,实际未达成接口实现)。
解决方法
方案1:抽象队列行为(推荐)
通过定义接口封装队列操作,让DataManager依赖抽象而非具体实现,彻底解决Moq无法Mock密封类的问题。
- 定义队列操作接口:
public interface IDataQueue { void Enqueue(ISampleData item); // 可按需添加Dequeue、Count等其他队列方法 }
- 实现接口的包装类:
public class DataQueue : IDataQueue { private readonly Queue<ISampleData> _innerQueue = new Queue<ISampleData>(); public void Enqueue(ISampleData item) { _innerQueue.Enqueue(item); } }
- 修正
ISampleData为接口:
public interface ISampleData { int ID { get; set; } double Data { get; set; } } public class SampleData : ISampleData { public int ID { get; set; } public double Data { get; set; } }
- 修改
DataManager依赖抽象:
public class DataManager { private readonly IDataQueue _dataQueue; public DataManager(IDataQueue dataQueue) { _dataQueue = dataQueue; } public bool InsertData(ISampleData sampleData) { try { _dataQueue.Enqueue(sampleData); return true; } catch(Exception) { return false; } } }
- 更新测试代码:
[Fact] public void Test_EnqueueMethod_With_Exception() { // Arrange var mockQueue = new Mock<IDataQueue>(); var dataManager = new DataManager(mockQueue.Object); mockQueue.Setup(x => x.Enqueue(It.IsAny<ISampleData>())).Throws(new Exception()); // Act var actual = dataManager.InsertData(new SampleData() { ID = 1001, Data = 20.9876 }); // Assert Assert.False(actual); }
方案2:使用真实队列模拟异常(局限场景)
如果不想修改业务依赖,可利用真实队列,但仅能模拟特定场景的异常(比如传入会抛出异常的对象):
[Fact] public void Test_EnqueueMethod_With_Exception() { // Arrange var realQueue = new Queue<ISampleData>(); var dataManager = new DataManager(realQueue); // 自定义一个访问属性时会抛出异常的SampleData var badData = new BadSampleData(); // Act var actual = dataManager.InsertData(badData); // Assert Assert.False(actual); } public class BadSampleData : ISampleData { public int ID { get => throw new Exception(); set {} } public double Data { get; set; } }
这种方法仅适用于队列操作会触发对象成员访问的场景,通用性差,不推荐作为常规方案。
内容的提问来源于stack exchange,提问作者The Kingmaker
相关产品推荐
相关产品推荐

