Mocking in Dotnet Core
October 16, 2019
What is mocking?
Mocking is one of the fundamentals we as developers need in our toolkit when it comes to writing tests. With mocking we can isolate and control the behavior of dependencies. Mocking can be a challenging concept to grasp for developers. in this post we’ll look at breaking down some examples of mocking in c# with the aim that by the end of this post you’ll have a better understanding of how mocking powers us to write explicit behavior driven unit tests.
Why should i use a mock?
There’s plenty of reasons to why we should be using mocks in our unit tests, some of the common reasons you’ll see around the internet are
- Speed, Faster failures means faster fixes.
- Control the behavior - Want to test how the code handles an exception? easy, make the mock throw an exception.
- Be more confident in your code
- Run tests without internet & in an isolated development environment.
Let’s write a test using a mock
Okay so let’s say you’re a developer & you’ve been tasked with sending a message to Queue to process a new shipping order. You’ll need to write some sort of Queue helper that makes it easier to send, receive & delete messages. here’s an example of what we might come up with to send our shipping order to the queue. It’s important to note that there is a few third party packages that can be used, in these examples we will be using Moq which can be downloaded here
public SQSHelper(IAmazonSQS client){_client = client;}
We pass our dependancy into our constructor as this will allow us to swap it out with a mock later when we need to write our tests. in most cases this will be injected using Dependancy injection.
public async Task SendMessage(string url,string message){try{SendMessageRequest sendMessageRequest = new SendMessageRequest();sendMessageRequest.QueueUrl = url;sendMessageRequest.MessageBody = message;await _client.SendMessageAsync(sendMessageRequest);}catch(AmazonSQSException ex){throw new Exception(ex.Message);}}
The SendMessage function is quite straight forward, it takes a QueueURl & our Message we wish to send, Creates the objects needed for the SendMessageAsync function then calls SendMessageAsync. Any Exceptions that might be thrown are caught, then raised for the caller to handle.
To use this helper we would create a new instance of the helper like this.
var Client = new AmazonSQSClient();var SQSHelper = new SQSHelper(Client);
When it comes to testing this function, we're looking to test the behavior rather then the outcome of the function. the behavior in our unit test will be testing that the our code calls SendMessageAsync, with the correct parameters we passed and that it's only called once.
Here’s how we go about testing that behavior
public void test_message_can_be_sent_to_sqs_okay(){var QueueUrl = "https://someamazonqueue.com"var Message = "Mocking in C#"// Define our Mock. i.e What are we trying to mock?var mockAmazonSQSClient = new Mock<IAmazonSQS>();// This is the argument that we expect will be passed to the function under testvar sendMessageRequest = new SendMessageRequest {QueueUrl = QueueUrl, MessageBody = Message};// Setup the mock behavior.mockAmazonSqsClient.Setup(mock => mock.SendMessageAsync(sendMessageRequest, default)).Verifiable();// Create an instance of our class under test// We need to pass mock.Object this will fake it as an object, otherwise it'll throw a type exception.var SQSHelper = new SQSHelper(mockAmazonSQSClient.Object)// Call our method under test.sqsHelper.SendMessage(QueueUrl, Message);//This is where we make our assertions using the Mock.mockAmazonSqsClient.Verify(mock => mock.SendMessageAsync(It.Is<SendMessageRequest>(r => sendMessageRequest.QueueUrl == QueueUrl &&r.MessageBody == Message), default), Times.Once());}
What we’ve done here is we’ve inserted a Mock in place of where we would usually have a AmazonSQSClient this let’s use setup ‘expectations’ of what we want the mock to do when a function or value is called on the mock. in our example we’re verifying that that the sendMessageAsync was called.
When it comes to the Verify stage here, what we’re doing is saying that SendMessageAsync was Called Once and Once only, it was called with an Object that is of the SendMessageRequestType and the params of that object are the Queue Url we passed and the MessageBody is the messageBody we passed, the default param is an async cancellation token that is required.
Let’s look at testing how our code handles exceptions.
Okay so we’ve written a test and now we know that our code is working, but what happens if an error occurs, will this code also throw an exception? Luckily we can test this with our Mock.
We’ll be testing the same function as before, let’s get to writing the test.
public void test_send_message_handles_exceptions_okay(){var QueueUrl = "https://someamazonqueue.com"var Message = "Mocking in C#"// Define our Mock. i.e What are we trying to mock?var mockAmazonSQSClient = new Mock<IAmazonSQS>();// This is the argument that we expect will be passed to the function under testvar sendMessageRequest = new SendMessageRequest {QueueUrl = QueueUrl, MessageBody = Message};// Setup the mock behavior.mockAmazonSqsClient.Setup(mock => mock.SendMessageAsync(receiveMessageRequest, default)).Throws(new AmazonSQSException("Error Connecting to SQS"));// Create an instance of our class under test// We need to pass mock.Object this will fake it as an object, otherwise it'll throw a type exception.var SQSHelper = new SQSHelper(mockAmazonSQSClient.Object)// Call our method under test.Assert.ThrowsAsync<AmazonSQSException>(() => sqsHelperClass.SendMessage(QueueUrl, Message));//This is where we make our assertions using the Mock.mockAmazonSqsClient.Verify(mock => mock.SendMessageAsync(It.Is<SendMessageRequest>(r => sendMessageRequest.QueueUrl == QueueUrl &&r.MessageBody == Message), default), Times.Once());}
this test is quite similar to our first one, the two differences are that we’ve setup our mock to throw an exception once SendMessageAsync is called. & We’re Asserting that an AmazonSQSException is thrown. We’ve now successfully tested that our code is tested to run as we expect to which is the working case & we’ve also tested how our code handles exceptions.
Summary
Mocking is invaluable when it comes to unit testing, keep practicing and it'll make sense in due time. The investment in learning now will pay off as your codebase grows and will reduce the time that your unit tests take as the codebase grows.