Dependencies are very common. For example, we use a database to store data, and we use an ORM framework to load or save data; we use an external web service to get the cinemas by location provided, or even in our code, we encapsulate some logic into a separate class, so that we can easily reuse the logic in different places.
When performing TDD, we often have code that has dependencies and can’t be run within the context of a test. In this case, we can use mock to get rid of the dependencies.
A mock is an object that acts as a substitute to help test scenarios that can’t otherwise be tested, they allow us to call the same signature and query the code. Sometimes referred to as ‘test doubles’, they can be likened to a stunt-double for an actor in the movie.
There are scenarios that we can use mock in the test:
In this section, we will continue to use the example in the previous blog to demonstrate mocks.
We already have a nice Greeting class. Can we now allow sending the greeting to different mediums (e.g. Facebook, WhatsApp,..)? All mediums are different, but they have the same behaviour-accepting the greeting message and showing it on itself. We use interfaces to define the behaviour. An interface is a contract specifying a set of methods and properties which will be implemented by any objects that implement the interface.
We will define an ISend interface, and add a Send method, which accepts a greeting message as parameter. After doing this, we can then pass the ISend dependency into Greeter’s constructor.
Also, we will use a Moq lib for mocking. The test will look like this:
The definition of the ISend will look as below:
We also need to add a SendGreeting method to Greeter class to ensure the code is successfully compiled.
After all of these, the test will fail for the correct reason. Then we will implement the SendGreeting method to make the test pass.
We already have the ability to send greetings to different mediums, but now we want to send the date along with the greeting message, so that the medium will know when the greeting should be displayed.
There are two scenarios:
Let’s start with scenario 1. First, we add a new test, and pass in the date created to the Send method.
We need to update the ISend interface, also Add a dateToSend parameter to the Greeter’s SendGreeting method. You may also notice that we modified the existing test ShouldSendTheGreeting by passing It.IsAny<DateTime>() to the Send method. It’s provided by Moq, which is telling the test to ignore the value during the verification. The code for ISend and Greeter will look as below.
We already make the dateToSend parameter optional here, because we already know the caller can ignore it in scenario 2. But initially you can make its type to DateTime first, and update it to DateTime? In scenario 2.
We run the test, and find the newly added test failed. Let’s update the code to make it pass.
Now it’s time to handle scenario 2. In the previous implementation, we already added DateTime.Now to the parameter, so it will be passed to the Send method if dateToSend is null.
If you still remember we updated an existing test to ignore the dateToSend parameter check. Now let's update the test to verify the date. The test will look like this.
But we will find that the test still fail, and the error message is a little bit confusing:
The reason is that the formatted datetime string ignored the milliseconds. The DateTime.Now we verified is not the same as the DateTime.Now we passed to the Send method. DateTime.Now is provided by the system, and we can’t change the value.
DateTime.Now is unpredictable, so in our test, we can use mock to get rid of the uncertainty. DateTime.Now is a static method, which can be mocked directly with Moq. The common way to handle this is to create a wrapper class, and then mock the wrapper. The wrapper is very thin, here is the code:
We also update the test as below:
Here we pass the wrapper into the constructor of class Greeter. The implementation will look like:
Run all the tests again, we will find all the tests passed.
Mock is very useful in TDD, but we should not rely on it too much. I saw some teams use mock to mock out all the dependencies which a class depends on, and test only the logic inside one test method. This is totally not recommended, because it’s more likely to test the implementation, not the functionality. What’s worse is that it prevents you from refactoring the code, because a small change may require you to update lots of tests that use mocks.
In these two blogs, we discussed why to use TDD and how to implement TDD. You should implement TDD in your projects as much as possible. But there are still some situations where TDD may not be suitable for.
Our team of consultants can teach you how. Leave your details and we will be in touch.