First REST Service
Let's start with creating a simple REST service returning a welcome message. We will create a simple POJO WelcomeBean
class with a member field called message and one argument constructor, as shown in the following code snippet:
package com.mastering.spring.springboot.bean; public class WelcomeBean { private String message; public WelcomeBean(String message) { super(); this.message = message; } public String getMessage() { return message; } }
Simple Method Returning String
Let's start with creating a simple REST Controller method returning a string:
@RestController public class BasicController { @GetMapping("/welcome") public String welcome() { return "Hello World"; } }
A few important things to note are as follows:
@RestController
: The@RestController
annotation provides a combination of@ResponseBody
and@Controller
annotations. This is typically used to create REST Controllers.@GetMapping("welcome")
:@GetMapping
is a shortcut for@RequestMapping(method = RequestMethod.GET)
. This annotation is a readable alternative. The method with this annotation would handle a Get request to thewelcome
URI.
If we run Application.java
as a Java application, it would start up the embedded Tomcat container. We can launch up the URL in the browser, as shown in the following screenshot:
Unit Testing
Let's quickly write a unit test to test the preceding controller
method:
@RunWith(SpringRunner.class) @WebMvcTest(BasicController.class) public class BasicControllerTest { @Autowired private MockMvc mvc; @Test public void welcome() throws Exception { mvc.perform( MockMvcRequestBuilders.get("/welcome") .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(content().string( equalTo("Hello World"))); } }
In the preceding unit test, we will launch up a Mock MVC instance with BasicController
. A few quick things to note are as follows:
@RunWith(SpringRunner.class)
: SpringRunner is a shortcut to theSpringJUnit4ClassRunner
annotation. This launches up a simple Spring context for unit testing.@WebMvcTest(BasicController.class)
: This annotation can be used along with SpringRunner to write simple tests for Spring MVC controllers. This will load only the beans annotated with Spring-MVC-related annotations. In this example, we are launching a Web MVC Test context with the class under test being BasicController.@Autowired private MockMvc mvc
: Autowires the MockMvc bean that can be used to make requests.mvc.perform(MockMvcRequestBuilders.get("/welcome").accept(MediaType.APPLICATION_JSON))
: Performs a request to/welcome
with theAccept
header valueapplication/json
.andExpect(status().isOk())
: Expects that the status of the response is 200 (success).andExpect(content().string(equalTo("Hello World")))
: Expects that the content of the response is equal to"Hello World"
.
Integration Testing
When we do integration testing, we would want to launch the embedded server with all the controllers and beans that are configured. This code snippet shows how we can create a simple integration test:
@RunWith(SpringRunner.class) @SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) public class BasicControllerIT { private static final String LOCAL_HOST = "http://localhost:"; @LocalServerPort private int port; private TestRestTemplate template = new TestRestTemplate(); @Test public void welcome() throws Exception { ResponseEntity<String> response = template .getForEntity(createURL("/welcome"), String.class); assertThat(response.getBody(), equalTo("Hello World")); } private String createURL(String uri) { return LOCAL_HOST + port + uri; } }
A few important things to note are as follows:
@SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
: It provides additional functionality on top of the SpringTestContext
. Provides support to configure the port for fully running the container andTestRestTemplate
(to execute requests).@LocalServerPort private int port
: TheSpringBootTest
would ensure that the port on which the container is running is autowired into theport
variable.private String createURL(String uri)
: The method to append the local host URL and port to the URI to create a full URL.private TestRestTemplate template = new TestRestTemplate()
: TheTestRestTemplate
is typically used in integration tests. It provides additional functionality on top ofRestTemplate
, which is especially useful in the integration test context. It does not follow redirects so that we can assert response location.template.getForEntity(createURL("/welcome"), String.class)
: It executes a get request for the given URI.assertThat(response.getBody(), equalTo("Hello World"))
: It asserts that the response body content is"Hello World"
.
Simple REST Method Returning an Object
In the previous method, we returned a string. Let's create a method that returns a proper JSON response. Take a look at the following method:
@GetMapping("/welcome-with-object") public WelcomeBean welcomeWithObject() { return new WelcomeBean("Hello World"); }
This preceding method returns a simple WelcomeBean
initialized with a message: "Hello World"
.
Executing a Request
Let's send a test request and see what response we get. The following screenshot shows the output:
The response for the http://localhost:8080/welcome-with-object
URL is shown as follows:
{"message":"Hello World"}
The question that needs to be answered is this: how does the WelcomeBean
object that we returned get converted into JSON?
Again, it's the magic of Spring Boot auto-configuration. If Jackson is on the classpath of an application, instances of the default object to JSON (and vice versa) converters are auto-configured by Spring Boot.
Unit Testing
Let's quickly write a unit test checking for the JSON response. Let's add the test to BasicControllerTest
:
@Test public void welcomeWithObject() throws Exception { mvc.perform( MockMvcRequestBuilders.get("/welcome-with-object") .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(content().string(containsString("Hello World"))); }
This test is very similar to the earlier unit test except that we are using containsString
to check whether the content contains a substring "Hello World"
. We will learn how to write proper JSON tests a little later.
Integration Testing
Let's shift our focus to writing an integration test. Let's add a method to BasicControllerIT
, as shown in the following code snippet:
@Test public void welcomeWithObject() throws Exception { ResponseEntity<String> response = template.getForEntity(createURL("/welcome-with-object"), String.class); assertThat(response.getBody(), containsString("Hello World")); }
This method is similar to the earlier integration test except that we are asserting for a sub-string using the String
method.
Get Method with Path Variables
Let's shift our attention to path variables. Path variables are used to bind values from the URI to a variable on the controller method. In the following example, we want to parameterize the name so that we can customize the welcome message with a name:
private static final String helloWorldTemplate = "Hello World, %s!"; @GetMapping("/welcome-with-parameter/name/{name}") public WelcomeBean welcomeWithParameter(@PathVariable String name) { return new WelcomeBean(String.format(helloWorldTemplate, name)); }
A few important things to note are as follows:
@GetMapping("/welcome-with-parameter/name/{name}")
:{name}
indicates that this value will be the variable. We can have multiple variable templates in a URI.welcomeWithParameter(@PathVariable String name)
:@PathVariable
ensures that the variable value from the URI is bound to the variable name.String.format(helloWorldTemplate, name)
: A simple string format to replace%s
in the template with the name.
Executing a Request
Let's send a test request and see what response we get. The following screenshot shows the response:
The response for the http://localhost:8080/welcome-with-parameter/name/Buddy
URL is as follows:
{"message":"Hello World, Buddy!"}
As expected, the name in the URI is used to form the message in the response.
Unit Testing
Let's quickly write a unit test for the preceding method. We would want to pass a name as part of the URI and check whether the response contains the name. The following code shows how we can do that:
@Test public void welcomeWithParameter() throws Exception { mvc.perform( MockMvcRequestBuilders.get("/welcome-with-parameter/name/Buddy") .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect( content().string(containsString("Hello World, Buddy"))); }
A few important things to note are as follows:
MockMvcRequestBuilders.get("/welcome-with-parameter/name/Buddy")
: This matches against the variable template in the URI. We pass in the nameBuddy
..andExpect(content().string(containsString("Hello World, Buddy")))
: We expect the response to contain the message with the name.
Integration Testing
The integration test for the preceding method is very simple. Take a look at the following test
method:
@Test public void welcomeWithParameter() throws Exception { ResponseEntity<String> response = template.getForEntity( createURL("/welcome-with-parameter/name/Buddy"), String.class); assertThat(response.getBody(), containsString("Hello World, Buddy")); }
A few important things to note are as follows:
createURL("/welcome-with-parameter/name/Buddy")
: This matches against the variable template in the URI. We are passing in the name, Buddy.assertThat(response.getBody(), containsString("Hello World, Buddy"))
: We expect the response to contain the message with the name.
In this section, we looked at the basics of creating a simple REST service with Spring Boot. We also ensured that we have good unit tests and integration tests. While these are really basic, they lay the foundation for more complex REST services we will build in the next section.
The unit tests and integration tests we implemented can have better asserts using a JSON comparison instead of a simple substring comparison. We will focus on it in the tests we write for the REST services we will create in the next sections.