I found myself writing a unit test for an Amazon SQS (IAmazonSQS) project I was working on. I was adding an SQS and wanted to insert my message along with some custom message attributes and in the end, I need a response back so I can grab the messageId for future use and troubleshooting.
Testing the method was challenging but not in a “this was fun” challenge. More like I am going to pull the rest of my hair out challenge.
So here is my .NET method to call the queue (SQS) and return the SendMessageResponse object:
public async Task<SendMessageResponse> SendMessage(IAmazonSQS sqsClient, string qUrl, string messageBody, Dictionary<string, MessageAttributeValue> attributes)
{
SendMessageRequest request = new SendMessageRequest();
request.QueueUrl = qUrl;
request.MessageBody = messageBody;
request.MessageAttributes = attributes;
request.MessageGroupId = Guid.NewGuid().ToString();
return await sqsClient.SendMessageAsync(request,CancellationToken.None);
}
Pretty standard code here. Nothing out of the ordinary. This is a FIFO type of SQS. Adding Message Attributes for another system that will consume this queue will do something with the body based on the message attributes.
I decided to use the mock library Moq to generate a mock for the IAmazonSQS. I could have implemented my own but there were so many methods to implement it wasn’t worth my time.
My first stab at a test looked like the following:
var _qUrl = "https://myqueue.url.fifo";
var _message = "{\"some\":\"test\"}";
var awsMock = new Mock<IAmazonSQS>();
//this is an internal class to retrieve some configuration settings
Mocks.MockConfigHelper myMockConfigHelper = new Mocks.MockConfigHelper();
//my internal service class constructor
AwsSqsService _myService = new AwsSqsService(myMockConfigHelper, awsMock.Object);
Dictionary<string, MessageAttributeValue> _attributes = new Dictionary<string, MessageAttributeValue>();
_attributes.Add("companyName", new MessageAttributeValue() { StringValue = "Test Company 1", DataType = "String" });
_attributes.Add("submitType", new MessageAttributeValue() { StringValue = "NEWCOMPANY", DataType = "String" });
SendMessageRequest _sendMessageRequest = new SendMessageRequest { QueueUrl = _qUrl, MessageBody = _message, MessageAttributes = _attributes,
MessageGroupId = Guid.NewGuid().ToString() };
SendMessageResponse _response = new SendMessageResponse()
{
MessageId = "testid",
HttpStatusCode = System.Net.HttpStatusCode.OK
};
awsMock.Setup(c => c.SendMessageAsync(_sendMessageRequest, CancellationToken.None)).ReturnsAsync(_response);
var result = await _myService.SendMessage(awsMock.Object, _qUrl, _message, _attributes);
Assert.IsNotNull(result);
I pained over this for hours. Everything looked right and it matched what we have done for other tests like this using a mock. But for some reason the mock IAmazonSQS interface object would always return null when executing this line:
return await sqsClient.SendMessageAsync(request,CancellationToken.None);
I wasn’t sure why and I scoured numerous Stack Overflow and examples on the web as to why this should work. But none of them were doing the exact same thing I was doing.
Finally I found a Stack Overflow answer that led me in the right direction. It seems that in this line:
awsMock.Setup(c => c.SendMessageAsync(_sendMessageRequest, CancellationToken.None)).ReturnsAsync(_response);
Even though the SendMessageAsync signature looked good and everything seemed to match, Moq will silently fail and the ReturnsAsync will always return null. To fix this you have to do the following:
awsMock.Setup(mock => mock.SendMessageAsync(It.IsAny<SendMessageRequest>(), CancellationToken.None)).ReturnsAsync(response);
The It.IsAny will make sure the mock matches any SendMessageRequest that it is given. I’m not sure exactly what my code above was sending differently but it was enough to cause issues.
Here is the final test method with all changes that will allow you to Moq an IAmazonSQS in .NET:
var _qUrl = "https://myqueue.url.fifo";
var _message = "{\"some\":\"test\"}";
var awsMock = new Mock<IAmazonSQS>();
//this is an internal class to retrieve some configuration settings
Mocks.MockConfigHelper myMockConfigHelper = new Mocks.MockConfigHelper();
//my internal service class constructor
AwsSqsService _myService = new AwsSqsService(myMockConfigHelper, awsMock.Object);
Dictionary<string, MessageAttributeValue> _attributes = new Dictionary<string, MessageAttributeValue>();
_attributes.Add("companyName", new MessageAttributeValue() { StringValue = "Test Company 1", DataType = "String" });
_attributes.Add("submitType", new MessageAttributeValue() { StringValue = "NEWCOMPANY", DataType = "String" });
SendMessageResponse _response = new SendMessageResponse()
{
MessageId = "testid",
HttpStatusCode = System.Net.HttpStatusCode.OK
};
awsMock.Setup(mock => mock.SendMessageAsync(It.IsAny<SendMessageRequest>(), CancellationToken.None)).ReturnsAsync(_response);
var result = await _myService.SendMessage(awsMock.Object, _qUrl, _message, _attributes);
Assert.IsNotNull(result);
So to recap if you are mocking an Amazon interface or another interface that you are not familiar with how their inner workings are handled make sure to use the It.IsAny<T>() method to make sure all your bases are covered.
Some notes about my code above.
- I was using .NET 6.0
- I was using Moq version 4.18.1
- I was using AWSSDK.SQS 3.7.2.72
- I am using Nunit also to run my tests
2 thoughts on “Testing Amazon IAmazonSQS in .NET using Moq”