I have the following class I wish to create unit tests for:
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();
}
}
It will be observed that it is dependent upon Microsoft.Azure.ServiceBus.Core.IMessageReceiver
.
My first attempt to mock this out was to use Moq
. I would have expected if I create a new Mock<IMessageReceiver>()
, I should be able to inject it into public ServiceBusClient(IMessageReceiver messageReceiver, int maximumMessages)
, but instead the compiler tells me "Error CS1503 Argument 1: cannot convert from 'Moq.Mock<Microsoft.Azure.ServiceBus.Core.IMessageReceiver>' to 'Microsoft.Azure.ServiceBus.Core.IMessageReceiver"....
Then I thought I would try to manually mock out the class:
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.
}
This will allow me to successfully provide messages EXCEPT the messages I provide don't include SystemProperties, so ServiceBusClient will throw an error at lockTokens.Add(message.SystemProperties.LockToken)
.
It turns out that the Microsoft.Azure.ServiceBus.Message
implementation does not provide a setter for public SystemPropertiesCollection SystemProperties
, so to set this (unless someone has a better way), I need to create my own implementation of Message:
public class MockMessage : Message {
public MockMessage(byte[] body) => base.Body = body;
public new SystemPropertiesCollection SystemProperties {
get { return this.SystemProperties; }
set { this.SystemProperties = value; }
}
}
Now, I can initialize SystemPropertiesCollection, BUT the problem becomes that no property in SystemPropertiesCollection actually includes a setting, so my tests will still fail.
Then I thought: Let's create a mock for "SystemPropertiesCollection" (never mind that we are starting to swim in the dangerous waters of "too much mock".... but when I try to extend this class, my compiler complains because SystemPropertiesCollection is actually a sealed class, so I can't extend it.
So, now I'm back to square one.
Any ideas how I can create good unit tests for ServiceBusClient?