This is the first blog post of a series that will evolve into a step-by-step guide for testing GWTP applications using TDD.
The goal of this first post is to give the readers basic knowledge that will serve as the foundation for the rest of the series.
In order to understand this post and apply the ideas that it contains, you should:
- Know how to use JUnit
- Understand the fundamentals of TDD [Red-Green-Refactor]
This post includes an overview of the following topics and also provides references for further details :
- Classic vs Mockist TDD
- Dependency Injection
This section is not made to bash either type of TDD or to say that one is better than the other. It is meant to show the strengths and the weaknesses of each type and to show the context in which each of them is relevant. The two styles are not mutually exclusive, but rather complementary.
This approach was first popularized by Kent Beck. The idea behind this kind of TDD is to test an algorithm multiple times by using different inputs and validating the outputs. Typically, this involves having tests on a class and hiding what’s happening in the background, i.e. hiding the direct dependencies of the class under test.
For example, if the class’ outputs are only dictated by its inputs, then a classic TDD test is easy to write. (e.g. to test a sorting algorithm, you just have to give it multiple inputs and have the corresponding expected outputs to verify it).
Unfortunately, not all situations are like that. Take for example an application that fetches data from an external source like a web API. Testing this involves more than checking outputs based on specific inputs. The actual output of the class will depend on its environment.
Mockist TDD – also called London School TDD – approach was popularized by Steve Freeman and Nat Pryce, and is focused on verifying the interactions of a class within its ecosystem. It is also a good tool for driving the design of an application.
One of the strengths of Mockist TDD is that it helps us organize the layers and the dependencies of an application. For example, many tests written using this kind of TDD will involve checking that a class delegates an action to the correct collaborator with the correct values. Other tests might consist in verifying that the tested class reacts correctly to its collaborators’ responses.
These kinds of tests help demonstrate that the communication doesn’t bypass layers in the application. It also helps ensuring that the Tell don’t ask principle is respected.
When testing GWTP applications, these concepts are very useful, because most testing will be interaction oriented.
Here’s a demonstration of J.B. Rainsberger developing an application using Mockist TDD.
Most of the time, when a class starts doing too much job, it is wise to split it up in smaller pieces. When testing a class, one of the best practices is to isolate it from the outside world (anything too complex, unpredictable or that doesn’t belong in the same level of abstraction).
Here’s a quote from James Shore that defines Dependency Injection :
“Dependency Injection” is a 25-dollar term for a 5-cent concept. That’s not to say that it’s a bad term… and it’s a good tool. But the top articles on Google focus on bells and whistles at the expense of the basic concept. I figured I should say something, well, simpler.
Dependency injection means giving an object its instance variables. Really. That’s it.
Basically, this lets us control the outside environment. For example, given a class “
A" that makes an HTTP request to an API and uses the data to do some computation, the constructor of
A would receive an instance of the class
B that is responsible of making that HTTP request. Class
A would only use the data provided by
In a testing context, one could pass an instance of
A with controlled and predictable return values.
There are multiple ways of making objects behave in a controlled/predictable way. Such objects are usually called “test doubles”. In testing jargon, the different types of test doubles are:
In a Mockist TDD context, one would pass
A‘s collaborators by constructor parameters and then test the behaviour of
A when it interacts with them. Such behaviour can be verified using a mock object.
A Mock is an object that has expectations of interactions with other objects. It is used to verify that a call which was expected to happen really did happen.
The tools used for the examples are:
As said before, classic TDD is easier to use in an algorithm context. Let’s take as an example the Leap Years Kata.
Write a function that returns true or false depending on whether its input integer is a leap year or not.
A leap year is defined as one that is divisible by 4, but is not otherwise divisible by 100 unless it is also divisible by 400.
For example, 2001 is a typical common year and 1996 is a typical leap year, whereas 1900 is an atypical common year and 2000 is an atypical leap year.
A typical code base written with classical TDD would end up with several test cases trying different inputs and expecting specific outputs.
It is hard to come up with simple examples that illustrate where mockist TDD really shines. We will try to do it with an example involving a Web API. Let’s imagine an endpoint that returns a username when it is given the user’s id.
Some people would arguably remove the first test because the behaviour it is verifying is also covered in the other tests. This is a matter of personal preference.
The design that emerges from these tests is directly linked to the use of mockist TDD, and by the objective of getting a piece of code that’s as easy as possible to test. This is where the collaborating classes (the factory and the user information service) came from.
There’s unfortunately no definitive answer. Both are good, just in different situations. It’s mostly a question of trade-offs and of preference. Here are a few points to consider:
- Classic TDD is better suited for algorithm testing
- Mockist TDD is better suited for to check interactions between components of a system
- Mockist TDD will create many collaborating classes early in the process. Classic TDD will push the creation of collaborators to later in the process.
- A code base written with mockist TDD is harder to refactor because a change in a collaborator’s API is likely to break a test.
- Some people suggest looking into ATDD to help with this problem.
- A code base written with classic TDD is easier to refactor; an implementation change doesn’t break tests
- Mockist TDD leads to an end-to-end implementation easily when using a Walking Skeleton.
Classic TDD works better when testing algorithms and Mockist TDD when verifying interactions between the components of a system.
Using dependency injection typically reveals a class’ dependencies explicitly, and allows us to substitute them easily with objects having controlled and predictable behaviours (test doubles).
The purpose of this post was to introduce the fundamental ideas that will serve as building blocks for later articles in this series. Don’t worry, there will be more code and less theory in the future posts.
The next article will be an overview of Jukito, a library built on top of JUnit and Mockito. Knowing how to use Jukito is pretty important. Although it is not strictly necessary for testing GWTP applications, it will be used for the rest of the series, since it saves a lot of time.
Subscribe to our newsletter to be notified when the next post comes out!