Appendix. Spring Boot microservices

Throughout the book, we’ve focused on developing microservices for Enterprise Java with Thorntail. This appendix provides details on developing microservices with Spring Boot. Included are snippets from Spring Boot in Action by Craig Walls (Manning, 2015). If you’re particularly focused on Spring Boot microservices, taking a look at this book for further details would be worthwhile (see www.manning.com/books/spring-boot-in-action).

Anatomy of a Spring Boot project

This section contains snippets from section 2.1.1 of Spring Boot in Action, outlining the parts of a Spring Boot application and its requirements.

Examining a newly initialized Spring Boot project

Figure 1 illustrates the structure of a Spring Boot reading-list project.

Figure 1. Structure of reading-list project

The first thing to notice is that the project structure follows the layout of a typical Maven or Gradle project. The main application code is placed in the src/main/java branch of the directory tree, resources are placed in the src/main/resources branch, and test code is placed in the src/test/java branch. At this point, you don’t have any test resources, but if you did, you’d put them in src/test/resources.

Digging deeper, you’ll see a handful of files sprinkled about the project:

  • build.gradleThe Gradle build specification
  • ReadingListApplication.javaThe application’s bootstrap class and primary Spring configuration class
  • application.propertiesA place to configure application and Spring Boot properties
  • ReadingListApplicationTests.javaA basic integration test class

The build specification contains a lot of Spring Boot goodness to uncover, so I’ll save inspection of it until last. Instead, we’ll start with ReadingListApplication.java.

Bootstrapping Spring

The ReadingListApplication class serves two purposes in a Spring Boot application: configuration and bootstrapping. First, it’s the central Spring configuration class. Even though Spring Boot autoconfiguration eliminates the need for a lot of Spring configuration, you’ll need at least a small amount of Spring configuration to enable autoconfiguration. As you can see in this listing, there’s only one line of configuration code.

Listing 1. ReadingListApplication
package readinglist;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication                                               1
public class ReadingListApplication {

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

}

  • 1 Enable component-scanning and autoconfiguration.
  • 2 Bootstrap the application.

@SpringBootApplication enables Spring component scanning and Spring Boot autoconfiguration. In fact, @SpringBootApplication combines three other useful annotations:

  • Spring’s @Configuration—Designates a class as a configuration class using Spring’s Java-based configuration. Although you won’t write a lot of configuration in this book, you’ll favor Java-based configuration over XML configuration when you do.
  • Spring’s @ComponentScan—Enables component scanning so that the web controller classes and other components you write will be automatically discovered and registered as beans in the Spring application context. Later in this appendix, you’ll write a simple Spring MVC controller that will be annotated with @Controller so that component scanning can find it.
  • Spring Boot’s @EnableAutoConfiguration—This humble little annotation might as well be named @Abracadabra because it’s the one line of configuration that enables the magic of Spring Boot autoconfiguration. This one line keeps you from having to write the pages of configuration that would be required otherwise.

In older versions of Spring Boot, you’d annotate the ReadingListApplication class with all three of these annotations. But since Spring Boot 1.2.0, @SpringBootApplication is all you need.

As I said, ReadingListApplication is also a bootstrap class. There are several ways to run Spring Boot applications, including traditional WAR file deployment. But for now, the main() method here enables you to run your application as an executable JAR file from the command line. It passes a reference to the ReadingListApplication class to SpringApplication.run(), along with the command-line arguments, to kick off the application.

Even though you haven’t written any application code, you can still build the application at this point and try it out. The easiest way to build and run the application is to use the bootRun task with Gradle:

$ gradle bootRun

The bootRun task comes from Spring Boot’s Gradle plugin. Alternatively, you can build the project with Gradle and run it with Java at the command line:

$ gradle build
...
$ java -jar build/libs/readinglist-0.0.1-SNAPSHOT.jar

The application should start up fine and enable a Tomcat server listening on port 8080. You can point your browser at http://localhost:8080 if you want, but because you haven’t written a controller class yet, you’ll be met with an HTTP 404 (Not Found) error and an error page. Before this appendix is finished, though, that URL will serve your reading-list application.

You’ll almost never need to change ReadingListApplication.java. If your application requires any additional Spring configuration beyond what Spring Boot autoconfiguration provides, it’s usually best to write it into separate @Configuration-configured classes. (They’ll be picked up and used by component scanning.) In exceptionally simple cases, though, you could add custom configuration to ReadingListApplication.java.

Testing Spring Boot applications

The Initializr also gives you a skeleton test class to help you get started with writing tests for your application. But ReadingListApplicationTests, shown in the following listing, is more than just a placeholder for tests. It also serves as an example of how to write tests for Spring Boot applications. @SpringApplicationConfiguration loads a Spring application context.

Listing 2. ReadingListApplicationTests
package readinglist;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;

import readinglist.ReadingListApplication;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(
         classes = ReadingListApplication.class)      1
@WebAppConfiguration
public class ReadingListApplicationTests {

  @Test
  public void contextLoads() {                        2
  }

}

  • 1 Load context via Spring Boot.
  • 2 Test that the context loads.

In a typical Spring integration test, you’d annotate the test class with @ContextConfiguration to specify how the test should load the Spring application context. But in order to take full advantage of Spring Boot magic, the @SpringApplicationConfiguration annotation should be used instead. As you can see in listing 2, ReadingListApplicationTests is annotated with @SpringApplicationConfiguration to load the Spring application context from the ReadingListApplication configuration class.

ReadingListApplicationTests also includes one simple test method, contextLoads(). It’s so simple, in fact, that it’s an empty method. But it’s sufficient for the purpose of verifying that the application context loads without any problems. If the configuration defined in ReadingListApplication is good, the test will pass. If any problems exist, the test will fail.

You’ll add some of your own tests as we flesh out the application. But the contextLoads() method is a fine start and verifies every bit of functionality provided by the application at this point.

Configuring application properties

The application.properties file given to you by the Initializr is initially empty. This file is optional, so you could remove it completely without impacting the application. But there’s also no harm in leaving it in place.

You’ll definitely find opportunity to add entries to application.properties later. For now, however, if you want to poke around with application.properties, try adding the following line:

server.port=8000

With this line, you’re configuring the embedded Tomcat server to listen on port 8000 instead of the default port 8080. You can confirm this by running the application again.

This demonstrates that the application.properties file comes in handy for fine-grained configuration of the stuff that Spring Boot automatically configures. But you can also use it to specify properties used by application code.

The main thing to notice is that at no point do you explicitly ask Spring Boot to load application.properties for you. By virtue of the fact that application.properties exists, it will be loaded and its properties made available for configuring both Spring and application code.

Spring Boot starter dependencies

This section provides information about the Spring Boot starters and how they’re used.

Using starter dependencies

To understand the benefit of Spring Boot starter dependencies, let’s pretend that they don’t exist. What kind of dependencies would you add to your build without Spring Boot? Which Spring dependencies do you need in order to support Spring MVC? Do you remember the group and artifact IDs for Thymeleaf, or any external dependency? Which version of Spring Data JPA should you use? Are all of these compatible?

Uh-oh. Without Spring Boot starter dependencies, you have some homework to do. All you want to do is develop a Spring web application with Thymeleaf views that persists its data via JPA. But before you can even write your first line of code, you have to figure out what needs to be put into the build specification to support your plan.

After much consideration (and probably a lot of copy-and-paste from another application’s build that has similar dependencies), you arrive at the following dependencies block in your Gradle build specification:

compile("org.springframework:spring-web:4.1.6.RELEASE")
compile("org.thymeleaf:thymeleaf-spring4:2.1.4.RELEASE")
compile("org.springframework.data:spring-data-jpa:1.8.0.RELEASE")
compile("org.hibernate:hibernate-entitymanager:jar:4.3.8.Final")
compile("com.h2database:h2:1.4.187")

This dependency list is fine and might even work. But how do you know? What kind of assurance do you have that the versions you chose for those dependencies are even compatible with each other? They might be, but you won’t know until you build the application and run it. And how do you know that the list of dependencies is complete? With not a single line of code having been written, you’re still a long way from kicking the tires on your build.

Let’s step back and recall what it is you want to do. You’re looking to build an application with these traits:

  • It’s a web application.
  • It uses Thymeleaf.
  • It persists data to a relational database via Spring Data JPA.

Wouldn’t it be simpler if you could specify those facts in the build and let the build sort out what you need? That’s exactly what Spring Boot starter dependencies do.

Specifying facet-based dependencies

Spring Boot addresses project dependency complexity by providing several dozen starter dependencies. A starter dependency is essentially a Maven POM that defines transitive dependencies on other libraries that together provide support for a certain functionality. Many of these starter dependencies are named to indicate the facet or kind of functionality they provide.

For example, the reading-list application is going to be a web application. Rather than add several individually chosen library dependencies to the project build, it’s much easier to simply declare that this is a web application. You can do that by adding Spring Boot’s web starter to the build.

You also want to use Thymeleaf for web views and persist data with JPA. Therefore, you need the Thymeleaf and Spring Data JPA starter dependencies in the build.

For testing purposes, you also want libraries that will enable you to run integration tests in the context of Spring Boot. Therefore, you also want a test-time dependency on Spring Boot’s test starter.

Taken altogether, you have the following five dependencies that the Initializr provides in the Gradle build:

dependencies {
  compile "org.springframework.boot:spring-boot-starter-web"
  compile "org.springframework.boot:spring-boot-starter-thymeleaf"
  compile "org.springframework.boot:spring-boot-starter-data-jpa"
  compile "com.h2database:h2"
  testCompile("org.springframework.boot:spring-boot-starter-test")
}

As you saw earlier, the easiest way to get these dependencies into your application’s build is to select the Web, Thymeleaf, and JPA check boxes in the Initializr. But if you didn’t do that when initializing the project, you can certainly go back and add them by editing the generated build.gradle or pom.xml.

Via transitive dependencies, adding these four dependencies is the equivalent of adding several dozen individual libraries to the build. Some of those transitive dependencies include such things as Spring MVC, Spring Data JPA, and Thymeleaf, as well as any transitive dependencies that those dependencies declare.

The most important thing to notice about the four starter dependencies is that they’re only as specific as they need to be. You don’t say that you want Spring MVC; you simply say that you want to build a web application. You don’t specify JUnit or any other testing tools; you just say that you want to test your code. The Thymeleaf and Spring Data JPA starters are a bit more specific, but only because there’s no less-specific way to declare that you want Thymeleaf and Spring Data JPA. The four starters in this build are only a few of the many starter dependencies that Spring Boot offers.

In no case did you need to specify the version. The starter dependencies’ versions are determined by which Spring Boot version you’re using. The starter dependencies themselves determine the versions of the various transitive dependencies that they pull in.

Not knowing what versions of the various libraries are used may be a little unsettling to you. Be encouraged to know that Spring Boot has been tested to ensure that all of the dependencies pulled in are compatible. It can be liberating to just specify a starter dependency and not have to worry about which libraries and which versions of those libraries you need to maintain.

But if you must know what you’re getting, you can always discover that from the build tool. In the case of Gradle, the dependencies task will give you a dependency tree that includes every library that your project is using and their versions:

$ gradle dependencies

You can get a similar dependency tree from a Maven build with the tree goal of the dependency plugin:

$ mvn dependency:tree

For the most part, you should never concern yourself with the specifics of what each Spring Boot starter dependency provides. Generally, it’s enough to know that the web starter enables you to build a web application, the Thymeleaf starter enables you to use Thymeleaf templates, and the Spring Data JPA starter enables data persistence to a database by using Spring Data JPA.

But what if, in spite of the testing performed by the Spring Boot team, there’s a problem with a starter dependency’s choice of libraries? How can you override the starter?

Overriding starter transitive dependencies

Ultimately, starter dependencies are just dependencies like any other dependency in your build. You can use the facilities of the build tool to selectively override transitive dependency versions, exclude transitive dependencies, and certainly specify dependencies for libraries not covered by Spring Boot starters.

For example, consider Spring Boot’s web starter. Among other things, the web starter transitively depends on the Jackson JSON library. This library is handy if you’re building a REST service that consumes or produces JSON resource representations. But if you’re using Spring Boot to build a more traditional human-facing web application, you may not need Jackson. Even though including it shouldn’t hurt anything, you can trim the fat off of your build by excluding Jackson as a transitive dependency.

If you’re using Gradle, you can exclude transitive dependencies like this:

compile("org.springframework.boot:spring-boot-starter-web") {
  exclude group: 'com.fasterxml.jackson.core'
}

In Maven, you can exclude transitive dependencies with the <exclusions> element. The following <dependency> for the Spring Boot web starter has <exclusions> to keep Jackson out of the build:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
  <exclusions>
    <exclusion>
      <groupId>com.fasterxml.jackson.core</groupId>
    </exclusion>
  </exclusions>
</dependency>

Conversely, maybe having Jackson in the build is fine, but you want to build against a different version of Jackson than what the web starter references. Suppose that the web starter references Jackson 2.3.4, but you’d rather use version 2.4.3. Using Maven, you can express the desired dependency directly in your project’s pom.xml file like this:

<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.4.3</version>
</dependency>

Maven always favors the closest dependency, meaning that because you’ve expressed this dependency in your project’s build, it’ll be favored over the one that’s transitively referred to by another dependency.

Similarly, if you’re building with Gradle, you can specify the newer version of Jackson in your build.gradle file like this:

compile("com.fasterxml.jackson.core:jackson-databind:2.4.3")

This dependency works in Gradle because it’s newer than the version transitively referred to by Spring Boot’s web starter. But suppose that instead of using a newer version of Jackson, you’d like to use an older version. Unlike Maven, Gradle favors the newest version of a dependency. Therefore, if you want to use an older version of Jackson, you have to express the older version as a dependency in your build and exclude it from being transitively resolved by the web starter dependency:

compile("org.springframework.boot:spring-boot-starter-web") {
  exclude group: 'com.fasterxml.jackson.core'
}
compile("com.fasterxml.jackson.core:jackson-databind:2.3.1")

In any case, be cautious when overriding the dependencies that are pulled in transitively by Spring Boot starter dependencies. Although different versions may work fine, a great amount of comfort can be taken from knowing that the versions chosen by the starters have been tested to play well together. You should override these transitive dependencies only under special circumstances (such as a bug fix in a newer version).

Now that you have an empty project structure and build specification ready, it’s time to start developing the application itself. As you do, you’ll let Spring Boot handle the configuration details while you focus on writing the code that provides the reading-list functionality.

Developing a Spring Boot application

In listing 3, you’ll further develop a Spring Boot application, with content from section 2.3.1 of Spring Boot in Action.

Focusing on application functionality

One way to gain an appreciation of Spring Boot autoconfiguration would be for me to spend the next several pages showing you the configuration that’s required in the absence of Spring Boot. But several great books on Spring could show you that, and showing it again wouldn’t help you get the reading-list application written any quicker.

Instead of wasting time talking about Spring configuration, know that Spring Boot is going to take care of that for you, so let’s see how taking advantage of Spring Boot autoconfiguration keeps you focused on writing application code. I can think of no better way to do that than to start writing the application code for the reading-list application.

Defining the domain

The central domain concept in your application is a book that’s on a reader’s reading list. Therefore, you’ll need to define an entity class that represents a book. Listing 3 shows how the Book type is defined.

Listing 3. The Book class
package readinglist;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Book {

  @Id
  @GeneratedValue(strategy=GenerationType.AUTO)
  private Long id;
  private String reader;
  private String isbn;
  private String title;
  private String author;
  private String description;

  public Long getId() {
    return id;
  }

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

  public String getReader() {
    return reader;
  }

  public void setReader(String reader) {
    this.reader = reader;
  }

  public String getIsbn() {
    return isbn;
  }

  public void setIsbn(String isbn) {
    this.isbn = isbn;
  }

  public String getTitle() {
    return title;
  }

  public void setTitle(String title) {
    this.title = title;
  }

  public String getAuthor() {
    return author;
  }

  public void setAuthor(String author) {
    this.author = author;
  }

  public String getDescription() {
    return description;
  }

  public void setDescription(String description) {
    this.description = description;
  }
}

As you can see, the Book class is a simple Java object with a handful of properties describing a book and the necessary accessor methods. It’s annotated with @Entity designating it as a JPA entity. The id property is annotated with @Id and @GeneratedValue to indicate that this field is the entity’s identity and that its value will be automatically provided.

Defining the repository interface

Next you need to define the repository through which the ReadingList objects will be persisted to the database. Because you’re using Spring Data JPA, that task is a simple matter of creating an interface that extends Spring Data JPA’s JpaRepository interface:

package readinglist;

import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ReadingListRepository extends JpaRepository<Book, Long> {

  List<Book> findByReader(String reader);
}

By extending JpaRepository, ReadingListRepository inherits 18 methods for performing common persistence operations. The JpaRepository interface is parameterized with two parameters: the domain type that the repository will work with, and the type of its ID property. In addition, I’ve added a findByReader() method through which a reading list can be looked up, given a reader’s username.

If you’re wondering about who will implement ReadingListRepository and the 18 methods it inherits, don’t worry too much about it. Spring Data provides a special magic of its own, making it possible to define a repository with just an interface. The interface will be implemented automatically at runtime when the application is started.

Creating the web interface

Now that you’ve defined the application’s domain and have a repository for persisting objects from that domain to the database, all that’s left is to create the web frontend. A Spring MVC controller like the one in the following listing will handle HTTP requests for the application.

Listing 4. ReadingListController
package readinglist;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import java.util.List;

@Controller
@RequestMapping("/")
public class ReadingListController {

  private ReadingListRepository readingListRepository;

  @Autowired
  public ReadingListController(
             ReadingListRepository readingListRepository) {
    this.readingListRepository = readingListRepository;
  }

  @RequestMapping(value="/{reader}", method=RequestMethod.GET)
  public String readersBooks(
      @PathVariable("reader") String reader,
      Model model) {

    List<Book> readingList =
        readingListRepository.findByReader(reader);
    if (readingList != null) {
      model.addAttribute("books", readingList);
    }
    return "readingList";
  }

  @RequestMapping(value="/{reader}", method=RequestMethod.POST)
  public String addToReadingList(
            @PathVariable("reader") String reader, Book book) {
    book.setReader(reader);
    readingListRepository.save(book);
    return "redirect:/{reader}";
  }

}

ReadingListController is annotated with @Controller in order to be picked up by component scanning and automatically registered as a bean in the Spring application context. It’s also annotated with @RequestMapping to map all of its handler methods to a base URL path of “/”.

The controller has two methods:

  • readersBooks()—Handles HTTP GET requests for /{reader} by retrieving a Book list from the repository (which was injected into the controller’s constructor) for the reader specified in the path. It puts the list of Book into the model under the key books and returns readingList as the logical name of the view to render the model.
  • addToReadingList()—Handles HTTP POST requests for /{reader}, binding the data in the body of the request to a Book object. This method sets the Book object’s reader property to the reader’s name, and then saves the modified Book via the repository’s save() method. Finally, it returns by specifying a redirect to /{reader} (which will be handled by the other controller method).

The readersBooks() method concludes by returning readingList as the logical view name. Therefore, you must also create that view. I decided at the outset of this project that we’d be using Thymeleaf to define the application views, so the next step is to create a file named readingList.html in src/main/resources/templates with the following content.

Listing 5. readingList.html
<html>
  <head>
    <title>Reading List</title>
    <link rel="stylesheet" th:href="@{/style.css}"></link>
  </head>

  <body>
    <h2>Your Reading List</h2>
    <div th:unless="${#lists.isEmpty(books)}">
      <dl th:each="book : ${books}">
        <dt class="bookHeadline">
          <span th:text="${book.title}">Title</span> by
          <span th:text="${book.author}">Author</span>
          (ISBN: <span th:text="${book.isbn}">ISBN</span>)
        </dt>
        <dd class="bookDescription">
          <span th:if="${book.description}"
                th:text="${book.description}">Description</span>
          <span th:if="${book.description eq null}">
                No description available</span>
        </dd>
      </dl>
    </div>
    <div th:if="${#lists.isEmpty(books)}">
      <p>You have no books in your book list</p>
    </div>

    <hr/>

    <h3>Add a book</h3>
    <form method="POST">
      <label for="title">Title:</label>
        <input type="text" name="title" size="50"></input><br/>
      <label for="author">Author:</label>
        <input type="text" name="author" size="50"></input><br/>
      <label for="isbn">ISBN:</label>
        <input type="text" name="isbn" size="15"></input><br/>
      <label for="description">Description:</label><br/>
        <textarea name="description" cols="80" rows="5">
        </textarea><br/>
      <input type="submit"></input>
    </form>

  </body>
</html>

This template defines an HTML page that is conceptually divided into two parts. At the top of the page is a list of books that are in the reader’s reading list. At the bottom is a form that the reader can use to add a new book to the reading list.

For aesthetic purposes, the Thymeleaf template references a stylesheet named style.css. That file should be created in src/main/resources/static and look like this:

body {
    background-color: #cccccc;
    font-family: arial,helvetica,sans-serif;
}

.bookHeadline {
    font-size: 12pt;
    font-weight: bold;
}

.bookDescription {
    font-size: 10pt;
}

label {
    font-weight: bold;
}

This stylesheet is simple and doesn’t go overboard to make the application look nice. But it serves our purposes and, as you’ll soon see, serves to demonstrate a piece of Spring Boot’s autoconfiguration.

Believe it or not, that’s a complete application. Every single line has been presented to you in this appendix. Flip back through the previous pages, and see if you can find any configuration. Aside from the three lines of configuration in listing 1 (which turns on autoconfiguration), you didn’t have to write any Spring configuration.

Despite the lack of Spring configuration, this complete Spring application is ready to run. Let’s fire it up and see how it looks.

Spring Boot testing

This section provides information about testing with Spring Boot, by mocking parts of Spring MVC. This section contains content from section 4.2.1 of Spring Boot in Action.

Mocking Spring MVC

Since Spring 3.2, the Spring Framework has had a useful facility for testing web applications by mocking Spring MVC. This makes it possible to perform HTTP requests against a controller without running the controller within an actual servlet container. Instead, Spring’s Mock MVC framework mocks enough of Spring MVC to make it almost as though the application is running within a servlet container—but it’s not.

To set up a Mock MVC in your test, you can use MockMvcBuilders. This class offers two static methods:

  • standaloneSetup()—Builds a Mock MVC to serve one or more manually created and configured controllers
  • webAppContextSetup()—Builds a Mock MVC using a Spring application context, which presumably includes one or more configured controllers

The primary difference between these two options is that standaloneSetup() expects you to manually instantiate and inject the controllers you want to test, whereas webAppContextSetup() works from an instance of WebApplicationContext, which itself was probably loaded by Spring. The former is slightly more akin to a unit test in that you’ll likely use it only for focused tests around a single controller. The latter, however, lets Spring load your controllers as well as their dependencies for a full-blown integration test.

For our purposes, you’re going to use webAppContextSetup() so you can test the ReadingListController as it has been instantiated and injected from the application context that Spring Boot has autoconfigured.

The webAppContextSetup() takes a WebApplicationContext as an argument. Therefore, you need to annotate the test class with @WebAppConfiguration and use @Autowired to inject the WebApplicationContext into the test as an instance variable. This listing shows the starting point for your Mock MVC tests.

Listing 6. MockMvcWebTests
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(
      classes = ReadingListApplication.class)
@WebAppConfiguration                              1
public class MockMvcWebTests {
  @Autowired
  private WebApplicationContext webContext;       2

  private MockMvc mockMvc;

  @Before
  public void setupMockMvc() {
    mockMvc = MockMvcBuilders                     3
        .webAppContextSetup(webContext)
        .build();
  }
}

  • 1 Enables web context testing
  • 2 Injects WebApplicationContext
  • 3 Sets up MockMvc

The @WebAppConfiguration annotation declares that the application context created by SpringJUnit4ClassRunner should be a WebApplicationContext (as opposed to a basic non-web ApplicationContext).

The setupMockMvc() method is annotated with JUnit’s @Before, indicating that it should be executed before any test methods. It passes the injected WebApplicationContext into the webAppContextSetup() method and then calls build() to produce a MockMvc instance, which is assigned to an instance variable for test methods to use.

Now that you have MockMvc, you’re ready to write test methods. Let’s start with a simple test method that performs an HTTP GET request against /readingList and asserts that the model and view meet your expectations. The following homePage() test method does what you need:

@Test
public void homePage() throws Exception {
  mockMvc.perform(MockMvcRequestBuilders.get("/readingList"))
      .andExpect(MockMvcResultMatchers.status().isOk())
      .andExpect(MockMvcResultMatchers.view().name("readingList"))
      .andExpect(MockMvcResultMatchers.model().attributeExists("books"))
      .andExpect(MockMvcResultMatchers.model().attribute("books",
          Matchers.is(Matchers.empty())));
}

As you can see, a lot of static methods are being used in this test method, including static methods from Spring’s MockMvcRequestBuilders and MockMvcResultMatchers, as well as from the Hamcrest library’s Matchers. Before diving into the details of this test method, let’s add a few static imports so that the code is easier to read:

import static org.hamcrest.Matchers.*;
import static
org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static
org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

With those static imports in place, the test method can be rewritten like this:

@Test
public void homePage() throws Exception {
  mockMvc.perform(get("/readingList"))
      .andExpect(status().isOk())
      .andExpect(view().name("readingList"))
      .andExpect(model().attributeExists("books"))
      .andExpect(model().attribute("books", is(empty())));
}

Now the test method almost reads naturally. First it performs a GET request against /readingList. Then it expects that the request is successful (isOk() asserts an HTTP 200 response code) and that the view has a logical name of readingList. It also asserts that the model contains an attribute named books, but that attribute is an empty collection. It’s all straightforward.

The main thing to note is that at no time is the application deployed to a web server. Instead it’s run within a mocked-out Spring MVC, just capable enough to handle the HTTP requests you throw at it via the MockMvc instance.

Pretty cool, huh? Let’s try one more test method. This time you’ll make it a bit more interesting by sending an HTTP POST request to post a new book. You should expect that after the POST request is handled, the request will be redirected back to /readingList and that the books attribute in the model will contain the newly added book. The following listing shows how to use Spring’s Mock MVC to do this kind of test.

Listing 7. MockMvcWebTests
@Test
public void postBook() throws Exception {
mockMvc.perform(post("/readingList")                         1
       .contentType(MediaType.APPLICATION_FORM_URLENCODED)
       .param("title", "BOOK TITLE")
       .param("author", "BOOK AUTHOR")
       .param("isbn", "1234567890")
       .param("description", "DESCRIPTION"))
       .andExpect(status().is3xxRedirection())
       .andExpect(header().string("Location", "/readingList"));

Book expectedBook = new Book();                              2
expectedBook.setId(1L);
expectedBook.setReader("craig");
expectedBook.setTitle("BOOK TITLE");
expectedBook.setAuthor("BOOK AUTHOR");
expectedBook.setIsbn("1234567890");
expectedBook.setDescription("DESCRIPTION");

mockMvc.perform(get("/readingList"))                         3
       .andExpect(status().isOk())
       .andExpect(view().name("readingList"))
       .andExpect(model().attributeExists("books"))
       .andExpect(model().attribute("books", hasSize(1)))
       .andExpect(model().attribute("books",
                    contains(samePropertyValuesAs(expectedBook))));
}

  • 1 Performs POST request
  • 2 Sets up expected book
  • 3 Performs GET request

This test is a bit more involved; it’s two tests in one method. The first part posts the book and asserts the results from that request. The second part performs a fresh GET request against the homepage and asserts that the newly created book is in the model.

When posting the book, you must make sure that you set the content type to application/x-www-form-urlencoded (with MediaType.APPLICATION_FORM_URLENCODED) because that’s the content type that a browser will send when the book is posted in the running application. You then use the MockMvcRequestBuilders's param() method to set the fields that simulate the form being submitted. After the request has been performed, you assert that the response is a redirect to /readingList.

Assuming that much of the test method passes, you move on to part 2. First, you set up a Book object that contains the expected values. You’ll use this to compare with the value that’s in the model after fetching the homepage.

Then you perform a GET request for /readingList. For the most part, this is no different from the way you tested the homepage before, except that instead of an empty collection in the model, you’re checking that it has one item, and that the item is the same as the expected Book you created. If so, then your controller seems to be doing its job of saving a book when one is posted to it.

Summary

  • Select content from Spring Boot in Action covered additional details on developing microservices with Spring Boot.
  • Further details on developing Spring Boot microservices can be found in Spring Boot in Action (www.manning.com/books/spring-boot-in-action).
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.139.239.41