如何在单元测试中模拟Azure.Storage.Queue的QueueClient?
问题:如何优雅模拟AzureQueueService构造函数内的QueueClient?
我刚接触C#/.NET语言与框架,在对AzureQueueService类及其方法进行单元测试时遇到问题:该类的构造函数中初始化了Azure.Storage.Queue包的QueueClient,用于向Azure存储队列发送消息,但测试时无法模拟这个内部创建的QueueClient实例。
原业务代码
using Azure.Storage.Queues; using my_api.Helpers; namespace nlp_api.Services.Azure { public interface IAzureQueueService { Task SendMessageAsync(string message); } public class AzureQueueService : IAzureQueueService { private readonly QueueClient _queueClient; private readonly ILogger<AzureQueueService> _logger; public AzureQueueService(IConfiguration configuration, ILogger<AzureQueueService> logger, IAzureCredentialHelper azureCredentialHelper) { _logger = logger; // Get the Azure Storage Account URL and queue name from configuration var accountUrl = configuration["AzureConnection:QueueStorageUrl"]; var queueName = configuration["AzureConnection:QueueName"]; // Use the AzureCredentialHelper to get the appropriate TokenCredential var credential = azureCredentialHelper.GetCredential(); // Create a QueueServiceClient and then retrieve the specific queue client _queueClient = new QueueClient(new Uri($"{accountUrl}/{queueName}"), credential); } public async Task SendMessageAsync(string message) { try { // Send the plain-text message to the Azure Queue await _queueClient.SendMessageAsync(message); _logger.LogInformation("Successfully sent message to queue."); } catch (Exception ex) { _logger.LogError(ex, "Error occurred while sending message to the queue."); throw; } } } }
原测试代码(无法生效)
using Azure.Storage.Queues; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using my_api.Services.Azure; using Azure.Core; using my_api.Helpers; namespace nlp_api.UnitTests.Services.Azure { public class AzureQueueServiceTests { [Fact] public async Task SendMessageAsync_Successfully_Sends_Message() { // Arrange var mockQueueClient = new Mock<QueueClient>("https://mockstorageaccount.queue.core.windows.net", "mock-queue"); var mockLogger = new Mock<ILogger<AzureQueueService>>(); var mockCredentialHelper = new Mock<IAzureCredentialHelper>(); // Mock GetCredential to return a fake credential var mockTokenCredential = new Mock<TokenCredential>(); mockCredentialHelper.Setup(helper => helper.GetCredential()).Returns(mockTokenCredential.Object); var mockConfiguration = new Mock<IConfiguration>(); mockConfiguration.Setup(c => c["AzureConnection:QueueStorageUrl"]).Returns("https://mockstorageaccount.queue.core.windows.net"); mockConfiguration.Setup(c => c["AzureConnection:QueueName"]).Returns("mock-queue"); var queueService = new AzureQueueService(mockConfiguration.Object, mockLogger.Object, mockCredentialHelper.Object); // Mock the SendMessageAsync method to just complete without error mockQueueClient.Setup(client => client.SendMessageAsync(It.IsAny<string>(), default)); // Act await queueService.SendMessageAsync("Test message"); // Assert mockQueueClient.Verify(client => client.SendMessageAsync(It.Is<string>(msg => msg == "Test message"), default), Times.Once); mockLogger.Verify(logger => logger.LogInformation(It.IsAny<string>()), Times.Once); } } }
测试不生效的原因是:mockQueueClient并未关联到AzureQueueService构造函数内部创建的QueueClient实例。我曾尝试为QueueClient创建包装类并注入,但包装类本身又面临测试问题,且封装开销较大,希望找到更简洁优雅的解决方案。
解决方案
方法1:直接注入QueueClient到构造函数(最简洁)
将QueueClient的创建逻辑移到DI容器中,直接把QueueClient作为依赖注入到AzureQueueService,测试时直接传入Mock的QueueClient即可。
修改业务代码
public class AzureQueueService : IAzureQueueService { private readonly QueueClient _queueClient; private readonly ILogger<AzureQueueService> _logger; // 直接注入QueueClient,移除构造函数内的创建逻辑 public AzureQueueService(QueueClient queueClient, ILogger<AzureQueueService> logger) { _logger = logger; _queueClient = queueClient; } // SendMessageAsync方法保持不变 public async Task SendMessageAsync(string message) { try { await _queueClient.SendMessageAsync(message); _logger.LogInformation("Successfully sent message to queue."); } catch (Exception ex) { _logger.LogError(ex, "Error occurred while sending message to the queue."); throw; } } }
在DI容器中注册QueueClient
在Program.cs中添加注册逻辑:
builder.Services.AddSingleton<QueueClient>(sp => { var configuration = sp.GetRequiredService<IConfiguration>(); var azureCredentialHelper = sp.GetRequiredService<IAzureCredentialHelper>(); var accountUrl = configuration["AzureConnection:QueueStorageUrl"]; var queueName = configuration["AzureConnection:QueueName"]; var credential = azureCredentialHelper.GetCredential(); return new QueueClient(new Uri($"{accountUrl}/{queueName}"), credential); });
修改后的测试代码
using Azure.Storage.Queues; using Microsoft.Extensions.Logging; using my_api.Services.Azure; using Moq; namespace nlp_api.UnitTests.Services.Azure { public class AzureQueueServiceTests { [Fact] public async Task SendMessageAsync_Successfully_Sends_Message() { // Arrange var mockQueueClient = new Mock<QueueClient>(MockBehavior.Loose, "https://mockstorageaccount.queue.core.windows.net", "mock-queue"); var mockLogger = new Mock<ILogger<AzureQueueService>>(); // 直接将Mock的QueueClient注入到服务中 var queueService = new AzureQueueService(mockQueueClient.Object, mockLogger.Object); // 模拟SendMessageAsync的成功行为 mockQueueClient.Setup(client => client.SendMessageAsync(It.IsAny<string>(), default)) .Returns(Task.CompletedTask); // Act await queueService.SendMessageAsync("Test message"); // Assert mockQueueClient.Verify(client => client.SendMessageAsync(It.Is<string>(msg => msg == "Test message"), default), Times.Once); mockLogger.Verify(logger => logger.LogInformation(It.Is<string>(s => s == "Successfully sent message to queue.")), Times.Once); } } }
方法2:轻量工厂模式(适合需要保留创建逻辑封装的场景)
如果不想把QueueClient的创建逻辑直接放在DI容器中,可以用一个极简的工厂接口来封装创建逻辑,这样测试时Mock工厂返回我们的MockQueueClient。
添加工厂接口与实现
public interface IQueueClientFactory { QueueClient CreateQueueClient(); } public class QueueClientFactory : IQueueClientFactory { private readonly IConfiguration _configuration; private readonly IAzureCredentialHelper _azureCredentialHelper; public QueueClientFactory(IConfiguration configuration, IAzureCredentialHelper azureCredentialHelper) { _configuration = configuration; _azureCredentialHelper = azureCredentialHelper; } public QueueClient CreateQueueClient() { var accountUrl = _configuration["AzureConnection:QueueStorageUrl"]; var queueName = _configuration["AzureConnection:QueueName"]; var credential = _azureCredentialHelper.GetCredential(); return new QueueClient(new Uri($"{accountUrl}/{queueName}"), credential); } }
修改AzureQueueService构造函数
public class AzureQueueService : IAzureQueueService { private readonly QueueClient _queueClient; private readonly ILogger<AzureQueueService> _logger; public AzureQueueService(IQueueClientFactory queueClientFactory, ILogger<AzureQueueService> logger) { _logger = logger; _queueClient = queueClientFactory.CreateQueueClient(); } // SendMessageAsync方法保持不变 }
注册工厂到DI容器
builder.Services.AddSingleton<IQueueClientFactory, QueueClientFactory>();
对应的测试代码
[Fact] public async Task SendMessageAsync_Successfully_Sends_Message() { // Arrange var mockQueueClient = new Mock<QueueClient>(MockBehavior.Loose, "https://mockstorageaccount.queue.core.windows.net", "mock-queue"); var mockLogger = new Mock<ILogger<AzureQueueService>>(); var mockFactory = new Mock<IQueueClientFactory>(); // 让工厂返回Mock的QueueClient mockFactory.Setup(f => f.CreateQueueClient()).Returns(mockQueueClient.Object); var queueService = new AzureQueueService(mockFactory.Object, mockLogger.Object); mockQueueClient.Setup(client => client.SendMessageAsync(It.IsAny<string>(), default)) .Returns(Task.CompletedTask); // Act await queueService.SendMessageAsync("Test message"); // Assert mockQueueClient.Verify(client => client.SendMessageAsync(It.Is<string>(msg => msg == "Test message"), default), Times.Once); mockLogger.Verify(logger => logger.LogInformation(It.Is<string>(s => s == "Successfully sent message to queue.")), Times.Once); }
内容的提问来源于stack exchange,提问作者Tim
相关产品推荐
相关产品推荐

