15 minutes is how long it takes you to build a REST service with Spring Boot. It will only take you another 15 minutes to add unit tests.

As the title clearly puts it, this article is a sequel. In Part I, we have shown how you can have a Spring project running in no time, with a minimal upfront configuration, using Spring Boot.  Now, we will proceed with the development of the Spring Boot application.

In the current software development state of evolution, testing your code is never too much. And for that, nothing is better than adding unit tests which will test your methods automatically and continuously.

 

# Getting ready

We start by adding the dependencies needed in the pom.xml file:

<dependencies>
       	 <!-- testing -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

 

Note the <scope>test</scope> attribute which will inform maven that these dependencies are only needed when we are testing the application, they are not to be included in the final package.

The spring-boot-starter-test contains almost all dependencies needed to start testing; it will add the following ones:

  • junit
  • json-path
  • mockito
  • assertj-core

Let’s add the test class file. This file needs to be created in this directory structure: src/test/java/, and in the package com/cleverti with the name AppControllerTest.java.

@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class AppControllerTest {
}

 

That’s it! The application has a running test class.

The annotation @RunWith indicates that the class should use Spring Junit.

The annotation @SpringBootTest indicates the starting point of the application and creates the Application context.

But this will not run any test because actually there are no tests being done.

Before we come to that, we will need to configure and add some mandatory fields to test a REST controller.

     private MockMvc mockMvc;

    @Autowired
    private WebApplicationContext webApplicationContext;

    @Before
    public void setup() {
        mockMvc = webAppContextSetup(webApplicationContext).build();
    }

 

We need to configure the MockMvc with the application context. This object will perform the HTTP request for the unit tests.

The @Before annotation will inform that the method will run before any test is performed.

 

# Adding the tests

Remember that this application contains a simple AppController with a Get Response method.

We want to add unit tests to cover all the paths of this code.

There are at least 3 paths for this method:

1. If id is found in the database, it returns the AppModel object

2. If not found it will throw a RunTimeException

3. If id is invalid, a bad request will happen

1. Object is found

The first unit test will assert that the object is found on the database.

Before doing that, we will need to add data to the H2 database. Simply add an SQL script in the resources folder with this SQL:

insert into app_model values (2, 'hello test');

 

@Test
    public void getFound() throws Exception{
        mockMvc.perform(get("/2")).andExpect(status()
                .isOk())
                .andExpect(jsonPath("$.message", is("hello test")))
	    .andExpect(jsonPath("$.links.length()", is(1)))
                .andExpect(jsonPath("$.links[0].href", is("http://localhost/2")));
    }

 

The @Test annotation indicates that this method is a unit test. In this test, we perform a get operation to “/2” and expect an OK (200) response. After that, we will assert that the field message equals to “hello test”. Note that mockMvc adds the prefix URL path “http://localhost”.

Remember that this application implements HATEAOS, so the request will also return the self-reference of the get request. The test also asserts that in the last 2 lines.

JsonPath is a very cool library that will help query the results of the requests. More information can be found on the homepage.

 

2. Object not found

This test will check if the RuntimeException is thrown and the response is a NotFound (404).

@Test
    public void getNotFound() throws Exception{
        mockMvc.perform(get("/20")).andDo(print()).andExpect(status()
                .isNotFound());
    }

 

This is a simple test; the id “20” doesn’t exist in the database so the expected response is NotFound.

We can call print() method to return the response in the Console of the application, that is useful to debug the output.

 

3. Invalid request

The get request is expecting a long primitive type. What will happen when we add a String object?

This next unit test will check this.

@Test
    public void getInvalid() throws Exception{
        mockMvc.perform(get("/invalid")).andDo(print()).andExpect(status()
                .isBadRequest());
    }

What will happen is that the server will not accept the request and will return a BadRequest (400)

 

NotFound Exception

It’s not the best practice for the controller method to throw the parent RuntimeException. We will not be able to differentiate different Exception.

Let’s create a new class for that:

public class NotFoundException extends RuntimeException {

    public NotFoundException(@NonNull Class object, Long id) {
        super(String.format("Object [%s] with id [%d] not found", object.getSimpleName(), id));
    }
}

The Get method will then be:

@GetMapping(path = "/{id}")
    public Resource<AppModel> get(@PathVariable long id) {
         AppModel model = service.findById(id).orElseThrow(()
					-> new NotFoundException(AppModel.class, id));

         return assembler.toResource(model);
    }

 

And the exception controller handler will be:

@ControllerAdvice
public class ExceptionHandlingController {

    @ResponseBody
    @ExceptionHandler(NotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public VndErrors objectNotFound(NotFoundException ex) {
        return new VndErrors("logref", ex.getMessage());
    }
}

 

And finally, let’s rewrite the unit test for that.

@Test
    public void getNotFound() throws Exception{
        mockMvc.perform(get("/20")).andExpect(status()
                .isNotFound())
                .andExpect(
			jsonPath("$[0].message", is("Object [AppModel] with id [20] not found")));

 

That confirms that the text from NotFoundException is returned by the get method.

 

It’s as simple as that! As you can see, there is no reason to have a poorly tested application.

 

Written by Patrício Pereira | Developer at Cleverti