Test-Driven Development with Spring Boot REST API

I deal with integration tests for RESTful applications a lot, however, I had not particularly tried Test Driven Development (TDD) methodologies. Therefore, I decided to give it a try and I can now tell that I quite like it. I shall assume you already have some basic idea on TDD, therefore I shall forgo an introduction on TDD.

In this article, let us look at how we can adopt TDD methodology in implementing Spring Boot RESTful application.

Creating Spring Boot Application

To start an application with Spring Boot is really easy, we just need to include Spring Boot dependencies in Maven pom.xml and we need a Java class file.

Create a simple maven project in IDE of your choice. I have created in Eclipse. The project structure is as follows:

Picture1

The Maven POM contains the following:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

       <modelVersion>4.0.0</modelVersion>

       <groupId>com.swathisprasad.tdd</groupId>

       <artifactId>tdd-springboot</artifactId>

       <version>1.0.0-SNAPSHOT</version>

       <!—Spring Boot starter parent goes here -->

<properties>

              <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

              <java.version>1.8</java.version>

       </properties>

</project>

Edit the pom.xml and add the following Spring Boot dependencies. Here, we are adding spring-boot-starter-parent as a parent for our project.

<parent>

              <groupId>org.springframework.boot</groupId>

              <artifactId>spring-boot-starter-parent</artifactId>

              <version>2.0.4.RELEASE</version>

              <relativePath /> <!-- lookup parent from repository -->

  </parent>

Under dependencies section, add the following dependency.

<dependencies>

              <dependency>

                      <groupId>org.springframework.boot</groupId>

                      <artifactId>spring-boot-starter-web</artifactId>

              </dependency>

  </dependencies>

In combination with spring-boot-starter-parent, the spring-boot-starter-web dependency allows us to run the web application.

Here is the class which is responsible for running the application.

package com.swathisprasad.springboot;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

/**

 * @author swathi

 *

 */

@SpringBootApplication

public class Application {
                  /**

                   * @param args

                   */

                  public static void main(String[] args) {

                                    SpringApplication.run(Application.class, args);

                  }

}

So far we have created a simple Spring Boot application. We can test our application by running Application.java as a Java application.

As we are focusing on TDD methodologies in our article, let us first create a simple integration test. Edit the pom.xml file and add the following dependencies.

             <dependency>

                      <groupId>org.springframework.boot</groupId>

                      <artifactId>spring-boot-starter-actuator</artifactId>

              </dependency>

              <dependency>

                      <groupId>org.springframework.boot</groupId>

                      <artifactId>spring-boot-starter-test</artifactId>

                      <scope>test</scope>

              </dependency>

             <dependency>

                      <groupId>com.jayway.restassured</groupId>

                      <artifactId>rest-assured</artifactId>

                      <version>2.8.0</version>

                     <scope>test</scope>

              </dependency>

              <dependency>

                      <groupId>org.codehaus.groovy</groupId>

                      <artifactId>groovy-all</artifactId>

                      <version>2.4.5</version>

              </dependency>

Here, spring-boot-starter-actuator provides production ready feature such as monitoring our app, gathering metrics and so on. The spring-boot-starter-test allows to bootstrap Spring context before executing tests.

The Maven plugin rest-assured is a Java DSL for testing REST services. This plugin requires groovy-all to run the tests.

We will add maven-failsafe-plugin plugin to execute the integration tests.

<build>

              <plugins>

                      <plugin>

                             <groupId>org.apache.maven.plugins</groupId>

                             <artifactId>maven-failsafe-plugin</artifactId>

                             <executions>

                                    <execution>

                                           <id>integration-test</id>

                                           <goals>

                                                  <goal>integration-test</goal>

                                           </goals>

                                    </execution>

                                    <execution>

                                           <id>verify</id>

                                           <phase>verify</phase>

                                           <goals>

                                                  <goal>verify</goal>

                                           </goals>

                                    </execution>

                             </executions>

                      </plugin>

                      <plugin>

                             <groupId>org.springframework.boot</groupId>

                             <artifactId>spring-boot-maven-plugin</artifactId>

                             <executions>

                                    <execution>

                                           <id>pre-integration-test</id>

                                           <goals>

                                                  <goal>start</goal>

                                           </goals>

                                    </execution>

                                    <execution>

                                           <id>post-integration-test</id>

                                           <goals>

                                                  <goal>stop</goal>

                                           </goals>

                                    </execution>

                             </executions>

                      </plugin>

              </plugins>

       </build>

Here, we have also added spring-boot-maven-plugin with some execution tasks in order to execute the tests during the deployment process.

Let’s create a test which makes a simple GET request and expects some data in the response body.

package com.swathisprasad.springboot.controller;

import static com.jayway.restassured.RestAssured.*;

import static org.hamcrest.Matchers.*;

import org.junit.Before;

import org.junit.Test;

import org.junit.runner.RunWith;

import org.springframework.beans.factory.annotation.Value;

import org.springframework.boot.test.context.SpringBootTest;

import org.springframework.test.context.ContextConfiguration;

import org.springframework.test.context.TestPropertySource;

import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import com.swathisprasad.springboot.Application;

import com.jayway.restassured.RestAssured;

import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;

/**

 * @author swathi

 *

 */

@RunWith(SpringJUnit4ClassRunner.class)  

@ContextConfiguration(classes = Application.class) 

@TestPropertySource(value={"classpath:application.properties"})

@SpringBootTest(webEnvironment = WebEnvironment.DEFINED_PORT)

public class SpringRestControllerTest {

    @Value("${server.port}") 

    int port;

       @Test

       public void getDataTest() {

              get("/api/tdd/responseData").then().assertThat().body("data", equalTo("responseData"));

       }

       @Before

       public void setBaseUri () {

               RestAssured.port = port;

               RestAssured.baseURI = "http://localhost"; // replace as appropriate

       }

}

Note that we have added some annotations here to run the tests in web environment.

@RunWith(SpringJUnit4ClassRunner.class) :  Supports loading of spring application context.

@ContextConfiguration: Defines class-level metadata that is used to determine how to load and configure an ApplicationContext for integration tests.

@TestPropertySource: Used to configure the location of properties files.

SpringBootTest: Tells Spring Boot to go and look for a main configuration class (one with @SpringBootApplication for instance), and use that to start a Spring application context.

As we have not defined any REST endpoint yet, we can run the above test using mvn verify command to see the test fail.

Let’s fix our test by introducing a Rest controller.

package com.swathisprasad.springboot;

import org.springframework.web.bind.annotation.PathVariable;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;

import org.springframework.web.bind.annotation.RestController;

/**

 * @author swathi

 *

 */

@RestController

public class SpringRestController {

                  @RequestMapping(path = "/api/tdd/{data}", method= RequestMethod.GET)

                  public Response getData(@PathVariable("data") String data) {

                                    return new Response(data);

                  }
                 
                  //inner class
                  class Response {

                                    private String data;

                                    public Response(String data) {

                                                      this.data = data;

                                    }

                                    public String getData() {

                                                      return data;

                                    }

                  }

}

In the class above, we have a REST endpoint which accepts ‘data’ as an input and returns a new instance of ‘Response’ in the response body.

Let’s run again ‘mvn verify’ command. It should now run the tests successfully.

Wrapping Up

Developing our implementation with TDD enables us to define the requirements in the form of tests. This approach also helps to maintain our code to make it easier to read and maintain while still passing the tests.

The complete source code can be found on GitHub.