You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在单元测试中模拟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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 23:32:05