在 C# 中,是否可以模拟 IMessageReceiver 和相关类进行单元测试?

2024-03-10

我希望为以下课程创建单元测试:

    public class ServiceBusClient {
        private readonly IMessageReceiver messageReceiver;
        private readonly int maximumMessages;

        public ServiceBusClient(IMessageReceiver messageReceiver, int maximumMessages) {
            this.messageReceiver = messageReceiver;
            this.maximumMessages = maximumMessages;
        }

        public async Task<List<EnergyUser>> ReceiveEnergyUsersAsync() {
            List<EnergyUser> energyUsers = new List<EnergyUser>();
            List<string> lockTokens = new List<string>();
            
            this.ReceiveMessages()
                .ForEach((message) => {
                    if (message.Body != null) {
                        energyUsers.Add(JsonConvert.DeserializeObject<EnergyUser>(Encoding.UTF8.GetString(message.Body)));
                    }
                    lockTokens.Add(message.SystemProperties.LockToken);
                });

            _ = this.messageReceiver.CompleteAsync(lockTokens);
            return await Task.FromResult(energyUsers);
        }

        private List<Message> ReceiveMessages() {
            return this.messageReceiver.ReceiveAsync(this.maximumMessages)
                            .GetAwaiter()
                            .GetResult()
                            .ToList();
        }
    }

可以看出,它取决于Microsoft.Azure.ServiceBus.Core.IMessageReceiver.

我第一次尝试模拟这个是使用Moq。我希望如果我创建一个new Mock<IMessageReceiver>(),我应该能够将它注入public ServiceBusClient(IMessageReceiver messageReceiver, int maximumMessages),但编译器却告诉我“错误 CS1503 参数 1:无法从 'Moq.Mock' 转换为 'Microsoft.Azure.ServiceBus.Core.IMessageReceiver”......

然后我想我应该尝试手动模拟该类:

    internal class MockMessageReceiver : IMessageReceiver {
        public int ReceivedMaxMessgeCount { get; set; }
        public IList<Message> ReturnMessages { get; set; }
        Task<IList<Message>> IMessageReceiver.ReceiveAsync(int maxMessageCount) {
            this.ReceivedMaxMessgeCount = maxMessageCount;
            return Task.FromResult(this.ReturnMessages);
        }

        public IEnumerable<string> ReceivedLockTokens { get; set; }
        Task IMessageReceiver.CompleteAsync(IEnumerable<string> lockTokens) {
            this.ReceivedLockTokens = lockTokens;
            return Task.Delay(1);
        }

        // Many functions which do nothing just to satisfy the bloated interface.
}

这将使我能够成功提供消息,除非我提供的消息不包含 SystemProperties,因此 ServiceBusClient 将在以下位置抛出错误lockTokens.Add(message.SystemProperties.LockToken).

事实证明,Microsoft.Azure.ServiceBus.Message实现不提供设置器public SystemPropertiesCollection SystemProperties,因此要设置它(除非有人有更好的方法),我需要创建自己的 Message 实现:

    public class MockMessage : Message {
        public MockMessage(byte[] body) => base.Body = body;

        public new SystemPropertiesCollection SystemProperties {
            get { return this.SystemProperties; }
            set { this.SystemProperties = value; }
        }
    }

现在,我可以初始化 SystemPropertiesCollection,但问题是 SystemPropertiesCollection 中没有属性实际上包含设置,因此我的测试仍然会失败。

然后我想:让我们为“SystemPropertiesCollection”创建一个模拟(不要介意我们开始在“太多模拟”的危险水域中游泳......但是当我尝试扩展此类时,我的编译器会抱怨,因为 SystemPropertiesCollection 是实际上是一个密封类,所以我无法扩展它。

所以,现在我回到了第一点。

有什么想法可以为 ServiceBusClient 创建良好的单元测试吗?


TL;DR

遇到了同样的问题,并通过利用反射来访问类的私有成员来解决它Message.SystemPropertiesCollection.

更详细的解释

为了避免这种情况message.SystemProperties.LockToken抛出一个InvalidOperationException你必须设置Message.SystemPropertiesCollection.SequenceNumber属性为 -1 以外的值。但该属性的设置者是私有的,类Message.SystemPropertiesCollection是密封的,并且没有其他间接方法可以在不通过服务总线实际发送消息的情况下设置序列号。您不想在单元测试中执行此操作。

但作弊反思打电话给私人二传手Message.SystemPropertiesCollection.SequenceNumber解决了这个障碍,让我假装Message不会投掷的物体。

示例代码

该辅助方法生成Message不会投掷的物体。

private static Message GenerateSaneMessage()
{
    var message = new Message();
    var sysCollectionType = typeof(Message.SystemPropertiesCollection);
    sysCollectionType.GetProperty(nameof(Message.SystemPropertiesCollection.SequenceNumber))!
        .SetValue(message.SystemProperties, 0);
    return message;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 C# 中,是否可以模拟 IMessageReceiver 和相关类进行单元测试? 的相关文章

随机推荐