Spring Boot with TDD— Part I

Sheik Arbaz
9 min readApr 20, 2019

--

In this tutorial I will show you how to create simple RESTful service using Spring Boot while following TDD religiously.

Prerequisites:

  • Java
  • Basic Understanding of MVC, REST APIs and Unit tests

Tech Stack:

  1. JDK 11
  2. Spring Boot (2.1.4)
  3. Gradle
  4. JPA
  5. JUnit (5.3.1)
  6. Mockito
  7. Hamcrest

A bit of Background!

What is TDD?

TDD is a paradigm where you write a test case and do minimalistic changes to the code to make it pass along with the existing test cases. Martin Fowler explains it here. In my understanding doing the following things repetitively is TDD.

  • Quickly add a test
  • Run all the tests and see the new one fail
  • Make minimalistic changes to the code
  • Run all tests and see them all succeed
  • Refactor to remove duplication

REST

Layered Architecture

Client sends a request, it goes through above layers and gets it served.

  • To provide access to the application, I am going to expose a number of REST endpoints. To do this, We write a Rest Controller. For convenience, we’ll be using just controller to refer to a rest controller in this tutorial.
  • Controller interacts with the Service layer which has complete business logic, which does the actual struggle of serving a request.
  • Service layer has to interact with our database, right? For that purpose we use Java Persistence API(JPA).

What is Spring Boot?

Spring Boot is a brand new framework from the team at Pivotal, designed to simplify the bootstrapping and development of a new Spring application.

Sample Project!!!

We are going to build a simple todo application.

Let’s Start!

Project Creation

  1. Go to https://start.spring.io/
  2. Choose gradle project. Add Web, JPA, H2. You can understand what they’re for from there only. JPA is an API just like JDBC to let Java interact with the database. The database we’ll be using is H2 which is an in memory database.

Click on Generate Project button and it will download a zip file (todo.zip)

Unzip it and open it in your IDE. Let’s use IntelliJ.

Spring boot is an opinionated framework. It comes with an older version of JUnit but We’ll be using Junit 5.3.1.

Go to build.gradle and add the below snippet enable JUnit.

test {
systemProperty 'spring.profiles.active', 'test'
useJUnitPlatform()
reports {
junitXml.enabled = true
html.enabled = false
}
}

Go to build.gradle file and paste the below snippet inside dependencies.

testImplementation ('org.springframework.boot:spring-boot-starter-test') {
exclude group: "junit", module: "junit"
}
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.3.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.3.1'

Let’s start coding…!

First Functionality

Let’s build an API for fetching all the todos.

Controller

Let’s start with the first layer i.e., controller. Create a ToDoControllerTest.java inside the test folder. Remember TDD! We’ve to start with a test. We’ve to register this class as a spring test class which is tesing a MVC controller. For that, we’ve to mark this class with two annotations Extendwith and WebMVCTest.

While using annotations, if the IDE shows error on them, press Option/Alt + Enter and import the suggested classes/methods.

We need a mockMvc object. So, we’ve to create an object with new keyword. But Spring has a magical annotation called Autowired. Basically, Spring has an ApplicationContext where it already stores objects of the classes which are marked with annotations like Service, Component, etc. Whenever you need objects of such classes, use Autowired, it’ll give them to you.

In unit testing, we want each module to be tested independently. But, our Controller depends on the Service layer. Since we are only focused on the Controller code, it is natural to mock the Service layer code for our unit tests.

@ExtendWith(SpringExtension.class)
@WebMvcTest
class ToDoControllerTest {

@Autowired
MockMvc mockMvc;

@MockBean
private ToDoService toDoService;
}

You cannot compile this code as ToDoService doesn’t exist. Option/Alt + Enter on it and create the class. It’s a service, mark it with Service annotation.

@Service
class ToDoService {
}

Now, let’s write a test case for fetching all the todos.

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;

@ExtendWith(SpringExtension.class)
@WebMvcTest
class ToDoControllerTest {

@Autowired
MockMvc mockMvc;

@MockBean
private ToDoService toDoService;


@Test
void getAllToDos() throws Exception {
List<ToDo> toDoList = new ArrayList<ToDo>();
toDoList.add(new ToDo(1L,"Eat thrice",true));
toDoList.add(new ToDo(2L,"Sleep Twice",true));
when(toDoService.findAll()).thenReturn(toDoList);

mockMvc.perform(MockMvcRequestBuilders.get("/todos")
.contentType(MediaType.APPLICATION_JSON)
).andExpect(jsonPath("$", hasSize(2))).andDo(print());
}
}

We’ve to make sure that the correct modules are imported. Now, run the test case. Wait, Wait! This test doesn’t compile even. It’s showing error wherever ToDo is there. Because our entity ToDo doesn’t exist yet. Let’s create it and mark it with the annotation Entity.

@Entity
public class ToDo {
private long id;
private String text;
private boolean completed;

public ToDo(long id, String text, boolean completed) {

this.id = id;
this.text = text;
this.completed = completed;
}
}

As we’re building a todo service, our model will be an entity called todo with attributes as text(string), completed(boolean). Acknowledge other errors shown by the IDE for not importing the class/method.

We’ve one more error at toDoService.findAll(), the method is not defined in the service. For now, just keep it simple. Anyhow, the mocked method is called in our test.

public List<ToDo> findAll() {
return new ArrayList<>();
}

Now run the test case: Cntrl + shift + R for running the test suite we’re inside. Is it passing? No. It’s raising the following error

java.lang.AssertionError: No value at JSON path “$”

Congratulations!!! We’ve a failing a test case now which has to be passed with a minimalistic code change. The end point we’re trying to hit with get Method doesn’t exist yet. Let’s create it. Mark it as a RestController.

@RestController
public class ToDoController {

@Autowired
private ToDoService toDoService;

@GetMapping("/todos")
ResponseEntity<List<ToDo>> getAllToDos() {
return new ResponseEntity<>(toDoService.findAll(), HttpStatus.OK);
}
}

Hurrah!!! It’s passing.

Make sure that the structure looks like below.

Service

The next layer we can implement is the service layer. Create a ToDoServiceTest.java file inside the test folder.

Let’s write a test case for it. The subject under test(SUT) is ToDoService. We have to mock all the dependencies of the SUT. So, what is the dependency of ToDoService? ……

Yes. It’s repository which we’re going to mock it. Here, we don’t want to mock MVC as we’re not dealing with controllers. Mark the class with SpringBootTest annotation which is provided by Spring Boot to test it’s components.This annotation adds ExtendWith annotation implicitly.

@SpringBootTest
public class ToDoServiceTest {

@MockBean
private ToDoRepository toDoRepository;
}

Wait, Wait! But the IDE is showing error at

private ToDoRepository toDoRepository;

It’s because the class doesn’t exist. Option/Alt + Enter on it, it’ll ask to create the class. Define ToDoRepository class like below to make it a JPA repository of entity ToDo having a primary key of datatype Long.

@Repository("toDoRepository")
public interface ToDoRepository extends JpaRepository<ToDo, Long> {
}

No let’s write the first test case in ToDoServiceTest!

@Test
void getAllToDos(){
ToDo todoSample = new ToDo("Todo Sample 1",true);
toDoRepository.save(todoSample);
ToDoService toDoService = new ToDoService(toDoRepository);

List<ToDo> toDoList = toDoService.findAll();
ToDo lastToDo = toDoList.get(toDoList.size()-1);

assertEquals(todoSample.getText(), lastToDo.getText());
assertEquals(todoSample.isCompleted(), lastToDo.isCompleted());
assertEquals(todoSample.getId(), lastToDo.getId());
}

Wait, Wait! There’s an error at

ToDoService toDoService = new ToDoService(toDoRepository);

ToDoService doesn’t have the constructor yet. Add it so that we can inject repository. As of now, our ToDoService should look like below.

@Service
public class ToDoService {
private ToDoRepository toDoRepository;

public ToDoService(ToDoRepository toDoRepository) {
this.toDoRepository = toDoRepository;
}

public List<ToDo> findAll() {
return new ArrayList<>();
}
}

Now, if you run the test case, it will raise an exception saying

java.lang.IllegalStateException: Failed to load ApplicationContext

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘entityManagerFactory’ defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is org.hibernate.AnnotationException: No identifier specified for entity: com.example.todo.ToDo

This is because Hibernate is unable to interact with our Entity. To make it interact: create default, parametrized constructers and getters-setters for both the fields. Create a primary key id which gets generated each time by the JPA.

@Entity
public class ToDo {

@Id
@GeneratedValue
private long id;
private String text;
private boolean completed;

public ToDo(){

}

public ToDo(String text, boolean completed) {
this.text = text;
this.completed = completed;
}

public ToDo(Long id, String text, boolean completed) {
this.id = id;
this.text = text;
this.completed = completed;
}

public long getId() {
return id;
}

public void setId(long id) {
this.id = id;
}

public String getText() {
return text;
}

public void setText(String text) {
this.text = text;
}

public boolean isCompleted() {
return completed;
}

public void setCompleted(boolean completed) {
this.completed = completed;
}
}

Now run the test case: Ctrl + shift + R for running the test suite we’re inside. Is it passing?

No. It’s failing with the below message.

org.opentest4j.AssertionFailedError:
Expected :3
Actual :0

Congratulations!!! We’ve a failing a test case now which has to be passed with a minimalistic code change. Now we’ve a service which has access to the repository. JPA provides bunch of methods to query. We’ll be using findAll method for retrieving all the todos.

public List<ToDo> findAll() {
return toDoRepository.findAll();
}

Now, run the test case.

Hurrah! It’ passing. We’ve written our first test case using TDD. Instead of checking the size of the returned result, we can check the result(List<ToDo>) itself.

The findAll() method we’ve used on the todoRepository is provided by the JPA, we don’t need to implement anything now.

Till now, we’ve implemented Controller, Service layers while following TDD. What’s next? Should we test ToDoRepository too? Logically, we should not because we’re not going to implement anything inside it.

While testing service layer we’ve mocked ToDoRepository. So, we’re not sure whether the data is getting stored in the DB through repository as we’ve not tested it. Now, we’ve two contradictory statements.

The solution to this is: We will not mock repository while testing the service layer. This way, we can make sure data is getting stored in the DB and we can escape from testing repository.

Now go to ToDoServiceTest.java and mark todoRepository with Autowired rather than MockBean.

@Autowired
private ToDoRepository toDoRepository;

And modify the test as per above discussion.

@Test
void getAllToDos(){
ToDo todoSample = new ToDo("Todo Sample 1",true);
toDoRepository.save(todoSample);
ToDoService toDoService = new ToDoService(toDoRepository);

ToDo firstResult = toDoService.findAll().get(0);

assertEquals(todoSample.getText(), firstResult.getText());
assertEquals(todoSample.isCompleted(), firstResult.isCompleted());
assertEquals(todoSample.getId(), firstResult.getId());
}

Now, let’s talk about the structure of a test case. It follows AAA pattern(Arrange, Act, Assert).

Arrange: This is the first step of a unit test. Here we will arrange the test, in other words we will do the necessary setup of the test. For example, to perform the test we need to create an object of the targeted class, if necessary, then we need to create mock objects and other variable initialization, something like these.

Act: In this step we will execute the test. In other words we will do the actual unit testing and the result will be obtained from the test application. Basically we will call the targeted function in this step using the object that we created in the previous step.

Assert: This is the last step of a unit test. In this step we will check and verify the returned result with expected results.

Spring boot comes with a tomcat server. Now, let’s start the server and hit our API through a tool called postman.

First.. let’s start the server. Go to the file ToDoApplication.java and run it. See the port on which it is started. Generally, It would be 8080.

Get All ToDo’s

Do a GET Request → http://localhost:8080/todos/

Response from the GET request

We’re getting an empty list because we’ve not added any data to the DB and we haven’t yet implemented any API to store the data. So, let’s add some data in the application itself.

Now we need to initialize our database (H2) with sample data using the interface CommandLineRunner. It is used to to run specific pieces of code when an application is fully started.

Our ToDoApplication should look like below.

@SpringBootApplication
public class TodoApplication {

public static void main(String[] args) {
SpringApplication.run(TodoApplication.class, args);
}

@Bean
public CommandLineRunner setup(ToDoRepository toDoRepository) {
return (args) -> {
toDoRepository.save(new ToDo("Add a new test case", true));
toDoRepository.save(new ToDo("Make it fail", true));
toDoRepository.save(new ToDo("Do changes to the code", false));
toDoRepository.save(new ToDo("Pass the test", true));
};
}
}

Now, hit the API.

Response from the GET request

Congratulations! Now, we’re able to hit the API.

Now, we can remove that CommandLineRunner setup from Application.java, it was just to get few todos using the API we’ve built as it will cause the service tests to fail because the setup method cannot get a ToDoRepository instance when we run a test suite annotated with SpringBootTest.

Recap

So, now we are clear with the steps to be followed to implement an API.

  1. Write a controller test where we mock the service, implement it.
  2. Write a service test where we use the repository instance directly, implement it.

Continue reading part II !!!. Git Repository.

--

--