TDD a Spring Boot App with Java - Part One - Gradle

This post is the first in a series that walks through building a small RESTful API for a Todo application using a test driven approach.

The first steps to starting a new Spring Boot application typically involve configuration and dependency management. This post will review using Gradle as a build tool for a new Spring project. Future posts in the series will continue by starting to build a simple RESTful API by reviewing Spring Boot’s architecture, and how to test different components of a Spring Boot web application.

If you’re looking for a quick introduction to Spring, the excellent official Spring guide to building a REST service is a great place to start. If you’re interested in seeing a Test-First approach to writing Spring applications, come back here and follow this series. While we’ll create everything from scratch as an exercise for teaching concepts, for real-world Spring projects, I recommend using Spring Initializr, a handy website from Spring, to generate a starter repository.

Prerequisites

Make sure you have Java and Gradle installed. For this post, I’ll use Java 8, and Gradle 6. One of my favorite websites for learning new programming languages, exercism.io, has excellent documentation for how to install Java across many platforms.

Project Creation

Create a directory called hello-spring and navigate into that directory.

mkdir hello-spring && cd hello-spring

Create the directory structure:

mkdir -p src/main/java/hello

We’ll use Gradle as our build tool. We’ll create a file called build.gradle to define our dependencies and configure tasks that we can run to build the application.

touch build.gradle

Gradle Plugins

The build.gradle file is written in Groovey, a language that borrows concepts from Ruby and Java. At the top of the build.gradle file, create a plugins block. Within it, we’ll add some Gradle plugins that will give us access to some tasks to make it easy to build and test our Spring app.

plugins {
    id 'java'
    id 'org.springframework.boot' version '2.4.3'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
}

In this case, using id 'java' syntax will apply the JavaPlugin, which gives you access to a collection of tasks including ones to compile Java source files (assemble), produce an executable jar files (jar), and run your tests (test).

The next line, id 'org.springframework.boot' version '2.4.3', defines the Spring Boot Gradle plugin. This plugin gives us even more convenience tasks to build Spring Boot applications. For example, when the java plugin is applied along with the Spring Boot Gradle plugin, the Spring Boot Gradle plugin responds by creating the bootRun task, which allows us to build a jar file and run the app locally in one step. (more info here).

And finally we have the Spring Dependency management plugin. Similar to the java Gradle plugin, when the Spring dependency-management Gradle plugin is applied it affects how the Spring Boot plugin works: the Spring Boot plugin will automatically import the spring-boot-dependencies “Bill of Manifest” (also known as BOM), which is a curated list of dependencies used to build Spring Boot applications. This means all of Spring Boot’s dependencies for the given version of Spring Boot are automatically imported, and it further allows us to the omit version numbers for any declared dependencies that are managed by Spring Boot. More on this later.

In summary, the combination of the java, Spring Boot, and Spring Dependency Management plugins give you some tasks to build and run Spring applications while making it easy to manage dependencies. Run gradle tasks and it’ll print out all the available tasks you can run.

Next we will add a line with the key sourceCompatibility, which declares the Java version your source code is compatible with, which tells the compiler how to handle parsing your code. For this example, I’ll stick to Java 8, so the value in the gradle config is 1.8.

sourceCompatibility = '1.8'

Declaring Dependencies

Next we’ll add a repositories and dependencies section to our build.gradle file:

repositories {
    mavenCentral()
}

dependencies {
  implementation 'org.springframework.boot:spring-boot-starter-web'
  testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

Declaring mavenCentral with the repositories block allows us to specify dependencies within the dependencies block that are published to the maven central repository.

Within the dependencies block, we declare external dependencies required for our application at runtime with the keyword implementation. The spring-boot-starter-web is a dependency published on maven that bundles together many dependencies for building a RESTful web application. For example, it adds a Tomcat server and Spring MVC, so everything we need to start writing an app exposing a RESTful API.

We also declare spring-boot-starter-test as a testImplementation dependency which bundles together a collection of libraries for testing our app, including JUnit, AssertJ, Hamcrest, Mockito and MockMvc. testImplementation tells Gradle that these dependencies are required for compiling and running tests, but not our production application.

Notice that we can declare spring-boot-starter-web and spring-boot-starter-test without version numbers because we are using the dependency-management plugin. The plugin will ensure that both of those dependencies versions are compatible with the version number of Spring Boot declared with the Spring boot plugin, which is 2.4.3 for this example. There are ways to override this behavior, but one of the advantages of using Spring Boot is that we can delegate much of the dependency management to the framework itself, so we spend less time worrying about dependencies and more time building applications.

And lastly, the test task type with useJunitPlatform() gives Gradle native support to run our tests with JUnit. We can also add a testLogging block to configure more detailed logging on the command line when tests fail due to an exception:

test {
    useJUnitPlatform()
    testLogging {
        exceptionFormat = TestExceptionFormat.FULL
    }
}

Putting it all together, you will have a build.gradle file that looks like this:

plugins {
    id 'java'
    id 'org.springframework.boot' version '2.4.3'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
}

sourceCompatibility = '1.8'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

test {
    useJUnitPlatform()
    testLogging {
        exceptionFormat = TestExceptionFormat.FULL
    }
}

Now, we should be ready to write our very first test of the application. First, we can create a test directory. Gradle expects this directory structure to match the source code directory structure we created previously, which is the Maven convention:

mkdir -p src/test/java/main/hello

Now we should have an empty directory structure that looks like this:

src
├── main
│   └── java
│       └── hello
└── test
    └── java
        └── hello

And inside of the /src/test/java/hello directory, create a file called ApplicationTest.java that looks like this:

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class ApplicationTest {

    @Test
    void contextLoads() {
    }

}

The @SpringBootTest annotation gives us a bunch of features, mainly it allows us to start a Spring context, which the framework uses to create instances of classes and wire them up with their dependencies using Dependency Injection. Here, we can test that we can start up a Spring context by writing an empty test within a test class annotated with @SpringBootTest.

Running this test with gradle clean test should fail, giving us ajava.lang.IllegalStateException: Unable to find a @SpringBootConfiguration

To make this test pass, we just need to create a class that uses the convenience annotation @SpringBootApplication, which in turn sets up the missing @SpringBootConfiguration. Inside the the src/main/java/hello directory, create a new file called Application.java with the code:

@SpringBootApplication
public class Application {
}

Now run gradle clean test. Your test should pass! So we successfully have started a Spring context, but our application still doesn’t do anything yet. In the next post we’ll start writing a simple web application that stores Todo tasks.

All of the source code for this post is located here. Thanks for reading!