如何用Moq验证队列创建逻辑中被捕获的特定聚合异常
如何用Moq验证CreateQueueIfNotExists捕获MessagingEntityAlreadyExistsException?
我有一个按需创建队列的方法CreateQueueIfNotExists:队列已存在则返回false,创建成功则返回true。现在需要测试这样的场景:调用QueueExists检查时队列不存在,但在调用CreateQueueAsync前被其他进程创建(也就是CreateQueueAsync会抛出MessagingEntityAlreadyExistsException,被方法内部捕获)。
产品代码
internal bool QueueExists(string queueName) { var allQueues = ManagementClient.GetQueuesAsync().Result; if (!allQueues.Any(q => q.Path.Equals(queueName))) { return false; } return true; } public bool CreateQueueIfNotExists(string queueName, bool requiresSession) { if (!QueueExists(queueName)) { try { ManagementClient.CreateQueueAsync(queueDescription).Wait(); return true; } catch (AggregateException ae) { Exception e = ae.GetBaseException(); // 如果队列已存在则无需额外操作 if (e is MessagingEntityAlreadyExistsException) { return false; } else { ExceptionDispatchInfo.Throw(e); } } } else { return false; } }
现有NUnit测试
[Test] public void QueueCreation_MessagingEntityAlreadyExistsException_IsCatched() { // Given var managementClientMock = new Mock<ManagementClient>(connectionString) { CallBase = true }; managementClientMock.Setup(m => m.GetQueuesAsync(It.IsAny<int>(), It.IsAny<int>(), It.IsAny<System.Threading.CancellationToken>())) .Returns(Task.FromResult<IList<QueueDescription>>(new List<QueueDescription>())); QueueFactory queueFactory = new QueueFactory(managementClientMock.Object); Assert.That(queueFactory.CreateQueueIfNotExists(alreadyExistsQueue, true), Is.True, "第一次队列创建未返回true"); Assert.That(queueFactory.CreateQueueIfNotExists(alreadyExistsQueue, true), Is.False, "第二次队列创建未返回false"); managementClientMock.Verify(m => m.CreateQueueAsync(It.IsAny<QueueDescription>(), It.IsAny<System.Threading.CancellationToken>()), Times.Exactly(2)); }
当前测试能验证CreateQueueAsync的调用次数,但我需要验证第二次调用时,CreateQueueAsync抛出的包含MessagingEntityAlreadyExistsException的AggregateException被方法正确捕获。由于异常已被业务方法内部处理,ExpectedException和Assert.Throws都不适用,请问能否用Moq实现这个验证?
解决方案:用Moq模拟抛出指定异常并验证行为
当然可以!核心思路是让Moq在第二次调用CreateQueueAsync时返回一个已失败的Task,内部包含MessagingEntityAlreadyExistsException,然后验证你的方法是否正确捕获这个异常并返回false,同时没有向外抛出异常。
修改后的测试代码如下:
[Test] public void QueueCreation_MessagingEntityAlreadyExistsException_IsCatched() { // Given var connectionString = "your-test-connection-string"; var alreadyExistsQueue = "test-queue"; var managementClientMock = new Mock<ManagementClient>(connectionString) { CallBase = true }; // 模拟GetQueuesAsync始终返回空列表,让QueueExists认为队列不存在 managementClientMock.Setup(m => m.GetQueuesAsync(It.IsAny<int>(), It.IsAny<int>(), It.IsAny<CancellationToken>())) .Returns(Task.FromResult<IList<QueueDescription>>(new List<QueueDescription>())); // 用SetupSequence设置CreateQueueAsync的两次调用行为: // 第一次调用:成功完成(模拟队列创建成功) // 第二次调用:返回包含MessagingEntityAlreadyExistsException的失败Task var expectedException = new MessagingEntityAlreadyExistsException("队列已存在"); managementClientMock.SetupSequence(m => m.CreateQueueAsync(It.IsAny<QueueDescription>(), It.IsAny<CancellationToken>())) .Returns(Task.CompletedTask) .Returns(Task.FromException<object>(new AggregateException(expectedException))); QueueFactory queueFactory = new QueueFactory(managementClientMock.Object); // When & Then // 第一次调用:应该返回true,因为队列创建成功 Assert.That(queueFactory.CreateQueueIfNotExists(alreadyExistsQueue, true), Is.True, "第一次队列创建未返回true"); // 第二次调用:应该返回false,因为CreateQueueAsync抛出的异常被捕获 // 同时这里不会向外抛出异常,说明异常被方法正确处理了 Assert.That(() => queueFactory.CreateQueueIfNotExists(alreadyExistsQueue, true), Throws.Nothing, "第二次调用意外抛出了异常"); Assert.That(queueFactory.CreateQueueIfNotExists(alreadyExistsQueue, true), Is.False, "第二次队列创建未返回false"); // 验证CreateQueueAsync确实被调用了两次 managementClientMock.Verify(m => m.CreateQueueAsync(It.IsAny<QueueDescription>(), It.IsAny<CancellationToken>()), Times.Exactly(2)); }
关键细节说明:
- SetupSequence的使用:通过
SetupSequence可以为同一个方法的多次调用设置不同的行为,完美匹配我们“第一次创建成功,第二次抛出异常”的测试场景。 - 模拟失败的Task:
Task.FromException<object>(new AggregateException(expectedException))会创建一个已失败的Task,当调用Wait()时会抛出AggregateException,和真实场景一致。 - 验证异常被捕获:用
Assert.That(() => ..., Throws.Nothing)来确认第二次调用方法时没有向外抛出异常,说明内部的异常捕获逻辑生效了。 - 返回值验证:第二次调用返回
false,符合方法的预期行为。
这样就能完整验证你的方法在并发场景下的异常处理逻辑啦!
内容的提问来源于stack exchange,提问作者fota666
相关产品推荐
相关产品推荐

