Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit Testing ServiceBus.Message. How to set value for SystemProperties.LockToken?

I want to test a message handler callback that I have registered with a QueueClient using queueClient.RegisterMessageHandler(MyCallBack, messageHandlerOptions).

public Task MyCallBack(Message msg, CancellationToken token)
{
   // Read msg. Do something

   // Since everything ran fine, complete the msg.
   await _client.CompleteAsync(msg.SystemProperties.LockToken);
}

Now as part of my unit test I call MyCallBack. Since I pass a valid message. I expect client.CompleteAsync() to be called. However the test throws an exception.

System.InvalidOperationException: Operation is not valid due to the current state of the object.

This is because msg.SystemProperties.LockToken is not set (which is because message was not actually read from a queue by a client with ReceiveMode.PeekLock mode).

Is there a way to set/mock this so that I can run my tests with a dummy string as a token?


PS: I know I can check for msg.SystemProperties.IsLockTokenSet before actually accessing the LockToken field; but even in that case I will never be able to unit test if _client.CompleteAsync() was called.

like image 266
GiriB Avatar asked May 23 '19 12:05

GiriB


2 Answers

Here is how to create a Message for testing and set the LockToken:

var message = new Message();
var systemProperties = message.SystemProperties;
var type = systemProperties.GetType();
var lockToken = Guid.NewGuid();
type.GetMethod("set_LockTokenGuid", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(systemProperties, new object[] { lockToken });
type.GetMethod("set_SequenceNumber", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(systemProperties, new object[] { 0 });

I wish that I had been able to come up with something that did not involve reflection.

like image 85
Andrew Radford Avatar answered Oct 17 '22 10:10

Andrew Radford


I created a wrapper method GetLockToken() which returns the LockToken string if it set on the message, else it returns null (instead of throwing an exception).

private string GetLockToken(Message msg)
{
    // msg.SystemProperties.LockToken Get property throws exception if not set. Return null instead.
    return msg.SystemProperties.IsLockTokenSet ? msg.SystemProperties.LockToken : null;
}

The original method call to CompleteAsync() is now modified to:

await _client.CompleteAsync(GetLockToken(message));

Note: The above change doesn't change the expected behavior! In a production scenario, call to CompleteAsync(null) would still throw an exception :) (as desired).

With above changes now I can set up my mocks as such:

var mock= new Mock<IQueueClient>();
mock.Setup(c => c.CompleteAsync(/*lockToken*/ null))
               .Returns(Task.CompletedTask);
like image 4
GiriB Avatar answered Oct 17 '22 09:10

GiriB