BLOG POST ·
10
min read ·
27.4.2020

TDD for beginners - Part 2

Development

Qingshan Zhuan

Qingshan provides deep capability in Agile Software Delivery, Solution Design and Implementation, and provides thought leadership to our clients.

Previously, we discussed the Test-Driven Development cycle and explained how it functions. In this article, we will discuss how to handle dependencies with TDD.

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:

  • Replace some functionalities which are not predictable.
  • Remove the dependency on other services.
  • Replace the dependency which is not related to current test purpose.
  • Simulate different conditions for the dependencies.
  • Test only the object in isolation instead of all the dependencies.

Lesson

In this section, we will continue to use the example in the previous blog to demonstrate mocks.

Our next requirement: Send a greeting to different mediums

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. 



Requirement 2: Send a greeting with a date

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:

  1. If the date is specified, then the message and the date should be sent to medium;
  2. If no date is specified, then send the message and the current DateTime to medium.

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.


When don't we use TDD?

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. 

  • Proof of concept: When you are doing a spike on a task or trying something to see if it works, you shouldn’t use TDD. TDD is for the case that the requirements are very clear and you know how to split the tasks and tackle them one by one.
  • Writing things that are going to be thrown away: TDD is used to ensure that the requirements are met, it’s a live document of the product code. If you know the code is thrown away soon, there is no means to add a test, or even use TDD for it.
  • 3rd parties of system libraries: You should never test a 3rd party library in your test. The library itself will take care of the correctness of itself. If you add a test for it, actually you are wasting your time to bring the extra effort to your test. This is about the responsibility and boundary between the system. If you have a common library that will be used in multiple applications. You should TDD your library code or add tests to your library codebase.
Related CONTENT

Need to implement Test Driven Development?

Our team of consultants can teach you how. Leave your details and we will be in touch.

Thanks for your message,
we will be in touch soon!
Oops! Something went wrong. Please try again later.