© Balaji Varanasi and Maxim Bartkov 2022
B. Varanasi, M. BartkovSpring RESThttps://doi.org/10.1007/978-1-4842-7477-4_9

9. Clients and Testing

Balaji Varanasi1   and Maxim Bartkov2
(1)
Salt Lake City, UT, USA
(2)
Kharkov, Ukraine
 
In this chapter we will discuss the following:
  • Building clients using RestTemplate

  • Spring Test framework basics

  • Unit testing MVC controllers

  • Integration testing MVC controllers

We have looked at building REST services using Spring. In this chapter, we will look at building clients that consume these REST services. We will also examine the Spring Test framework that can be used to perform unit and end-to-end testing of REST services.

QuickPoll Java Client

Consuming REST services involves building a JSON or XML request payload, transmitting the payload via HTTP/HTTPS, and consuming the returned JSON response. This flexibility opens doors to numerous options for building REST clients in Java (or, as a matter of fact, any technology). A straightforward approach for building a Java REST client is to use core JDK libraries. Listing 9-1 shows an example of a client reading a Poll using the QuickPoll REST API.
public void readPoll() {
        HttpURLConnection connection = null;
        BufferedReader reader = null;
        try {
                URL restAPIUrl = new URL("http://localhost:8080/v1/polls/1");
                connection = (HttpURLConnection) restAPIUrl.openConnection();
                connection.setRequestMethod("GET");
                // Read the response
reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
                StringBuilder jsonData = new StringBuilder();
                String line;
                while ((line = reader.readLine()) != null) {
                        jsonData.append(line);
                }
                System.out.println(jsonData.toString());
        }
        catch(Exception e) {
                e.printStackTrace();
        }
        finally {
                // Clean up
                IOUtils.closeQuietly(reader);
                if(connection != null)
                        connection.disconnect();
        }
}
Listing 9-1

Reading a Poll Using Java URLClass

Although there is nothing wrong with the approach in Listing 9-1, there is a lot of boilerplate code that needs to be written to perform a simple REST operation. The readPoll method would grow even bigger if we were to include the code to parse the JSON response. Spring abstracts this boilerplate code into templates and utility classes and makes it easy to consume REST services.

RestTemplate

Central to Spring’s support for building REST clients is the org.springframework.web.client.RestTemplate. The RestTemplate takes care of the necessary plumbing needed to communicate with REST services and automatically marshals/unmarshals HTTP request and response bodies. The RestTemplate like Spring’s other popular helper classes such as JdbcTemplate and JmsTemplate is based on the Template Method design pattern.1

The RestTemplate and associated utility classes are part of the spring-web.jar file . If you are building a standalone REST client using RestTemplate, you need to add the spring-web dependency, shown in Listing 9-2, to your pom.xml file.
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-web</artifactId>
  <version>5.3.9</version>
</dependency>
Listing 9-2

Spring-web.jar Dependency

RestTemplate provides convenient methods to perform API requests using six commonly used HTTP methods. In the next sections, we will look at some of these functions along with a generic yet powerful exchange method to build QuickPoll clients.

Note

In this chapter we will continue building on the work that we did on the QuickPoll application in the previous chapters. Alternatively, a starter project inside the Chapter9starter folder of the downloaded source code is available for you to use. The completed solution is available under the Chapter9final folder. Please refer to this solution for complete listings containing getters/setters and additional imports.

Getting Polls

RestTemplate provides a getForObject method to retrieve representations using the GET HTTP method. Listing 9-3 shows the three flavors of the getForObject method .
public <T> T getForObject(String url, Class<T> responseType, Object... urlVariables) throws  RestClientException {}
public <T> T getForObject(String url, Class<T> responseType, Map<String,?> urlVariables) throws RestClientException
public <T> T getForObject(URI url, Class<T> responseType) throws RestClientException
Listing 9-3

GetForObject Method Flavors

The first two methods accept a URI template string, a return value type, and URI variables that can be used to expand the URI template. The third flavor accepts a fully formed URI and return value type. RestTemplate encodes the passed-in URI templates, and, hence, if the URI is already encoded, you must use the third method flavor. Otherwise, it will result in double encoding of the URI, causing malformed URI errors.

Listing 9-4 shows the QuickPollClient class and the usage of getForObject method to retrieve a Poll for a given poll id. The QuickPollClient is placed under the com.apress.client package of our QuickPoll application and is interacting with the first version of our QuickPoll API. In the upcoming sections, we will create clients that interact with second and third versions of the API. RestTemplate is threadsafe, and, hence, we created a class-level RestTemplate instance to be used by all client methods. Because we have specified the Poll.class as the second parameter, RestTemplate uses HTTP message converters and automatically converts the HTTP response content into a Poll instance.
package com.apress.client;
import org.springframework.web.client.RestTemplate;
import com.apress.domain.Poll;
public class QuickPollClient {
        private static final String QUICK_POLL_URI_V1 = "http://localhost:8080/v1/polls";
        private RestTemplate restTemplate = new RestTemplate();
        public Poll getPollById(Long pollId) {
return restTemplate.getForObject(QUICK_POLL_URI_V1 + "/{pollId}", Poll.class, pollId);
        }
}
Listing 9-4

QuickPollClient and GetForObject Usage

This listing demonstrates the power of RestTemplate. It took about a dozen lines in Listing 9-1, but we were able to accomplish that and more with a couple of lines using RestTemplate. The getPollById method can be tested with a simple main method in QuickPollClient class:
public static void main(String[] args) {
        QuickPollClient client = new QuickPollClient();
        Poll poll = client.getPollById(1L);
        System.out.println(poll);
}
Note

Ensure that you have the QuickPoll application up and running before you run the main method.

Retrieving a Poll collection resource is a little trickier as providing List<Poll>.class as a return value type to the getForObject would result in compilation error. One approach is to simply specify that we are expecting a collection:
List allPolls =  restTemplate.getForObject(QUICK_POLL_URI_V1, List.class);

However, because RestTemplate can’t automatically guess the Java class type of the elements, it would deserialize each JSON object in the returned collection into a LinkedHashMap . Hence, the call returns all of our Polls as a collection of type List<LinkedHashMap>.

To address this issue, Spring provides a org.springframework.core.ParameterizedTypeReference abstract class that can capture and retain generic-type information at runtime. So, to specify the fact that we are expecting a list of Poll instances, we create a subclass of ParameterizedTypeReference :
ParameterizedTypeReference<List<Poll>> responseType = new ParameterizedTypeReference<List<Poll>>() {};
RestTemplate HTTP-specific methods such as getForObject don’t take a ParameterizedTypeReference as their parameter. As shown in Listing 9-5, we need to use RestTemplate’s exchange method in conjunction with ParameterizedTypeReference. The exchange method infers the return-type information from the passed-in responseType parameter and returns a ResponseEntity instance. Invoking the getBody method on ResponseEntity gives us the Poll collection.
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.ResponseEntity;
import org.springframework.http.HttpMethod;
public List<Poll> getAllPolls() {
ParameterizedTypeReference<List<Poll>> responseType = new ParameterizedTypeReference
<List<Poll>>() {};
ResponseEntity<List<Poll>> responseEntity = restTemplate.exchange(QUICK_POLL_URI_V1, HttpMethod.GET, null, responseType);
        List<Poll> allPolls = responseEntity.getBody();
        return allPolls;
}
Listing 9-5

Get All Polls Using RestTemplate

We can also accomplish similar behavior with getForObject by requesting RestTemplate to return an array of Poll instances:
Poll[] allPolls = restTemplate.getForObject(QUICK_POLL_URI_V1, Poll[].class);

Creating a Poll

RestTemplate provides two methods—postForLocation and postForObject—to perform HTTP POST operations on a resource. Listing 9-6 gives the API for the two methods.
public URI postForLocation(String url, Object request, Object... urlVariables) throws RestClientException
public <T> T postForObject(String url, Object request, Class<T> responseType, Object... uriVariables) throws RestClientException
Listing 9-6

RestTemplate’s POST Support

The postForLocation method performs an HTTP POST on the given URI and returns the value of the Location header. As we have seen in our QuickPoll POST implementations, the Location header contains the URI of the newly created resource. The postForObject works similar to postForLocation but converts a response into a representation. The responseType parameter indicates the type of representation to be expected.

Listing 9-7 shows the QuickPollClient’s createPoll method that creates a new Poll using the postForLocation method.
public URI createPoll(Poll poll) {
        return restTemplate.postForLocation( QUICK_POLL_URI_V1, poll);
}
Listing 9-7

Create a Poll Using PostForLocation

Update the QuickPollClient’s main method with this code to test the createPoll method:
public static void main(String[] args) {
        QuickPollClient client = new QuickPollClient();
        Poll newPoll = new Poll();
        newPoll.setQuestion("What is your favourate color?");
        Set<Option> options = new HashSet<>();
        newPoll.setOptions(options);
        Option option1 = new Option(); option1.setValue("Red"); options.add(option1);
        Option option2 = new Option(); option2.setValue("Blue");options.add(option2);
        URI pollLocation = client.createPoll(newPoll);
        System.out.println("Newly Created Poll Location " + pollLocation);
}

PUT Method

The RestTemplate provides the aptly named PUT method to support the PUT HTTP method. Listing 9-8 shows QuickPollClient’s updatePoll method that updates a poll instance. Notice that the PUT method doesn’t return any response and communicates failures by throwing RestClientException or its subclasses.
public void updatePoll(Poll poll) {
        restTemplate.put(QUICK_POLL_URI_V1 + "/{pollId}",  poll, poll.getId());
}
Listing 9-8

Update a Poll Using PUT

DELETE Method

The RestTemplate provides three overloaded DELETE methods to support DELETE HTTP operations. The DELETE methods follow semantics similar to PUT and don’t return a value. They communicate any exceptions via RestClientException or its subclasses. Listing 9-9 shows the deletePoll method implementation in QuickPollClient class.
public void deletePoll(Long pollId) {
        restTemplate.delete(QUICK_POLL_URI_V1 + "/{pollId}",  pollId);
}
Listing 9-9

Delete a Poll

Handling Pagination

In version 2 of our QuickPoll API, we introduced paging. So, the clients upgrading to version 2 need to re-implement the getAllPolls method. All other client methods will remain unchanged.

To re-implement the getAllPolls , our first instinct would be to simply pass the org.springframework.data.domain.PageImpl as the parameterized type reference:
ParameterizedTypeReference<PageImpl<Poll>> responseType = new ParameterizedTypeReference<PageImpl<Poll>>() {};
ResponseEntity<PageImpl<Poll>> responseEntity = restTemplate.exchange(QUICK_POLL_URI_2, HttpMethod.GET, null, responseType);
PageImpl<Poll> allPolls = responseEntity.getBody();
The PageImpl is a concrete implementation of the org.springframework.data.domain.Page interface and can hold all of the paging and sorting information returned by the QuickPoll REST API. The only problem with this approach is that PageImpl doesn’t have a default constructor and Spring’s HTTP message converter would fail with the following exception:
Could not read JSON: No suitable constructor found for type [simple type, class org.springframework.data.domain.PageImpl<com.apress.domain.Poll>]: can not instantiate from JSON object (need to add/enable type information?)
To handle pagination and successfully map JSON to an object, we will create a Java class that mimics PageImpl class but also has a default constructor, as shown in Listing 9-10.
package com.apress.client;
import java.util.List;
import org.springframework.data.domain.Sort;
public class PageWrapper<T> {
        private List<T> content;
        private Boolean last;
        private Boolean first;
        private Integer totalPages;
        private Integer totalElements;
        private Integer size;
        private Integer number;
        private Integer numberOfElements;
        private Sort sort;
        // Getters and Setters removed for brevity
}
Listing 9-10

PageWrapper Class

Note

There are occasions when you need to generate Java types from JSON. This is especially true for APIs that don’t provide a Java client library. The online tool www.jsonschema2pojo.org provides a convenient way to generate Java POJOs from JSON schema or JSON data.

The PageWrapper class can hold the returned content and has attributes to hold the paging information. Listing 9-11 shows the QuickPollClientV2 class that makes use of PageWrapper to interact with second version of API. Notice that the getAllPolls method now takes two parameters: page and size. The page parameter determines the requested page number, and the size parameter determines the number of elements to be included in the page. This implementation can be further enhanced to accept sort parameters and provide sorting functionality.
package com.apress.client;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import com.apress.domain.Poll;
public class QuickPollClientV2 {
        private static final String QUICK_POLL_URI_2 = "http://localhost:8080/v2/polls";
        private RestTemplate restTemplate = new RestTemplate();
        public PageWrapper<Poll> getAllPolls(int page, int size) {
ParameterizedTypeReference<PageWrapper<Poll>> responseType = new
ParameterizedTypeReference<PageWrapper<Poll>>() {};
                UriComponentsBuilder builder = UriComponentsBuilder
                                                .fromHttpUrl(QUICK_POLL_URI_2)
                                                .queryParam("page", page)
                                                .queryParam("size", size);
ResponseEntity<PageWrapper<Poll>> responseEntity = restTemplate.exchange
(builder.build().toUri(), HttpMethod.GET, null, responseType);
                return responseEntity.getBody();
        }
}
Listing 9-11

QuickPoll Client for Version 2

Handling Basic Authentication

Up to this point we have created clients for the first and second versions of QuickPoll API. In Chapter 8, we secured the third version of the API, and any communication with that version requires Basic authentication. For example, running a DELETE method on URI http://localhost:8080/v3/polls/3 without any authentication would result in an HttpClientErrorException with a 401 status code.

To successfully interact with our QuickPoll v3 API, we need to programmatically base 64 encode a user’s credentials and construct an authorization request header. Listing 9-12 shows such implementation: we concatenate the passed-in username and password. We then base 64 encode it and create an Authorization header by prefixing Basic to the encoded value.
import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.http.HttpHeaders;
private HttpHeaders getAuthenticationHeader(String username, String password) {
        String credentials = username + ":" + password;
        byte[] base64CredentialData = Base64.encodeBase64(credentials.getBytes());
        HttpHeaders headers = new HttpHeaders();
        headers.set("Authorization", "Basic " + new String(base64CredentialData));
        return headers;
}
Listing 9-12

Authentication Header Implementation

The RestTemplate’s exchange method can be used to perform an HTTP operation and takes in an Authorization header. Listing 9-13 shows the QuickPollClientV3BasicAuth class with deletePoll method implementation using Basic authentication.
package com.apress.client;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.web.client.RestTemplate;
import org.springframework.http.HttpEntity;
public class QuickPollClientV3BasicAuth {
        private static final String QUICK_POLL_URI_V3 = "http://localhost:8080/v3/polls";
        private RestTemplate restTemplate = new RestTemplate();
        public void deletePoll(Long pollId) {
HttpHeaders authenticationHeaders = getAuthenticationHeader("admin", "admin");
                restTemplate.exchange(QUICK_POLL_URI_V3 + "/{pollId}",
HttpMethod.DELETE, new HttpEntity<Void>(authenticationHeaders), Void.class, pollId);
        }
}
Listing 9-13

QuickPoll Client with Basic Auth

Note

In this approach, we have manually set the Authentication header to each request. Another approach is to implement a custom ClientHttpRequestInterceptor that intercepts each outgoing request and automatically appends the header to it.

Testing REST Services

Testing is an important aspect of every software development process. Testing comes in different flavors, and in this chapter, we will focus on unit and integration testing. Unit testing verifies that individual, isolated units of code are working as expected. It is the most common type of testing that developers typically perform. Integration testing typically follows unit testing and focuses on the interaction between previously tested units.

The Java ecosystem is filled with frameworks that ease unit and integration testing. JUnit and TestNG have become the de facto standard test frameworks and provide foundation/integration to most other testing frameworks. Although Spring supports both frameworks, we will be using JUnit in this book, as it is familiar to most readers.

Spring Test

The Spring Framework provides the spring-test module that allows you to integrate Spring into tests. The module provides a rich set of annotations, utility classes, and mock objects for environment JNDI, Servlet, and Portlet API. The framework also provides capabilities to cache application context across test executions to improve performance. Using this infrastructure, you can easily inject Spring beans and test fixtures into tests. To use the spring-test module in a non–Spring Boot project, you need to include the Maven dependency as shown in Listing 9-14.
<dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>3.5.9</version>
        <scope>test</scope>
</dependency>
Listing 9-14

Spring-test Dependency

Spring Boot provides a starter POM named spring-boot-starter-test that automatically adds the spring-test module to a Boot application. Additionally, the starter POM brings in JUnit, Mockito, and Hamcrest libraries:
  • Mockito is a popular mocking framework for Java. It provides a simple and easy-to-use API to create and configure mocks. More details about Mockito can be found at http://mockito.org/.

  • Hamcrest is a framework that provides a powerful vocabulary for creating matchers. To put it simply, a matcher allows you to match an object against a set of expectations. Matchers improve the way that we write assertions by making them more human readable. They also generate meaningful failure messages when assertions are not met during testing. You can learn more about Hamcrest at http://hamcrest.org/.

To understand the spring-test module , let’s examine a typical test case. Listing 9-15 shows a sample test built using JUnit and spring-test infrastructure.
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = QuickPollApplication.class)
@WebAppConfiguration
public class ExampleTest {
        @Before
        public void setup() { }
        @Test
        public void testSomeThing() {}
        @After
        public void teardown() { }
}
Listing 9-15

Sample JUnit Test

Our example test contains three methods—setup, testSomeThing, and teardown each annotated with a JUnit annotation. The @Test annotation denotes the testSomeThing as a JUnit test method. This method will contain code that ensures our production code works as expected. The @Before annotation instructs JUnit to run the setup method prior to any test method execution. Methods annotated with @Before can be used for setting up test fixtures and test data. Similarly, the @After annotation instructs JUnit to run the teardown method after any test method execution. Methods annotated with @After are typically used to tear down test fixtures and perform cleanup operations.

JUnit uses the notion of test runner to perform test execution. By default, JUnit uses the BlockJUnit4ClassRunner test runner to execute test methods and associated life cycle (@Before or @After, etc.) methods. The @RunWith annotation allows you to alter this behavior. In our example, using the @RunWith annotation, we are instructing JUnit to use the SpringJUnit4ClassRunner class to run the test cases.

The SpringJUnit4ClassRunner adds Spring integration by performing activities such as loading application context, injecting autowired dependencies, and running specified test execution listeners. For Spring to load and configure an application context, it needs the locations of the XML context files or the names of the Java configuration classes. We typically use the @ContextConfiguration annotation to provide this information to the SpringJUnit4ClassRunner class.

In our example, however, we use the SpringBootTest, a specialized version of the standard ContextConfiguration that provides additional Spring Boot features. Finally, the @WebAppConfiguration annotation instructs Spring to create the web version of the application context, namely, WebApplicationContext.

Unit Testing REST Controllers

Spring’s dependency injection makes unit testing easier. Dependencies can be easily mocked or simulated with predefined behavior, thereby allowing us to zoom in and test code in isolation. Traditionally, unit testing Spring MVC controllers followed this paradigm. For example, Listing 9-16 shows the code unit testing PollController’s getAllPolls method.
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import java.util.ArrayList;
import com.google.common.collect.Lists;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.test.util.ReflectionTestUtils;
public class PollControllerTestMock {
        @Mock
        private PollRepository pollRepository;
        @Before
        public void setUp() throws Exception {
                MockitoAnnotations.initMocks(this);
        }
        @Test
        public void testGetAllPolls() {
                PollController pollController  = new PollController();
ReflectionTestUtils.setField(pollController, "pollRepository", pollRepository);
                when(pollRepository.findAll()).thenReturn(new ArrayList<Poll>());
ResponseEntity<Iterable<Poll>> allPollsEntity = pollController.getAllPolls();
                verify(pollRepository, times(1)).findAll();
                assertEquals(HttpStatus.OK, allPollsEntity.getStatusCode());
                assertEquals(0, Lists.newArrayList(allPollsEntity.getBody()).size());
        }
}
Listing 9-16

Unit Testing PollController with Mocks

The PollControllerTestMock implementation uses the Mockito’s @Mock annotation to mock PollController’s only dependency: PollRepository. For Mockito to properly initialize the annotated pollRepository property, we either need to run the test using the MockitoJUnitRunner test runner or invoke the initMocks method in the MockitoAnnotations. In our test, we choose the latter approach and call the initMocks in the @Before method.

In the testGetAllPolls method , we create an instance of PollController and inject the mock PollRepository using Spring’s ReflectionTestUtils utility class. Then we use Mockito’s when and thenReturn methods to set the PollRepository mock’s behavior. Here are we indicating that when the PollRepository’s findAll() method is invoked, an empty collection should be returned. Finally, we invoke the getAllPolls method and verify findAll() method’s invocation and assert controller’s return value.

In this strategy, we treat the PollController as a POJO and hence don’t test the controller’s request mappings, validations, data bindings, and any associated exception handlers. Starting from version 3.2, spring-test module includes a Spring MVC Test framework that allows us to test a controller as a controller. This test framework will load the DispatcherServlet and associated web components such as controllers and view resolvers into test context. It then uses the DispatcherServlet to process all the requests and generates responses as if it is running in a web container without actually starting up a web server. This allows us to perform a more thorough testing of Spring MVC applications.

Spring MVC Test Framework Basics

To gain a better understanding of the Spring MVC Test framework, we explore its four important classes: MockMvc, MockMvcRequestBuilders, MockMvcResultMatchers, and MockMvcBuilders. As evident from the class names, the Spring MVC Test framework makes heavy use of Builder pattern.2

Central to the test framework is the org.springframework.test.web.servlet.MockMvc class , which can be used to perform HTTP requests. It contains only one method named perform and has the following API signature:
public ResultActions perform(RequestBuilder requestBuilder) throws java.lang.Exception
The RequestBuilder parameter provides an abstraction to create the request (GET, POST, etc.) to be executed. To simplify request construction, the framework provides an org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder implementation and a set of helper static methods in the org.springframework.test.web.servlet.request.MockMvcRequestBuilders class. Listing 9-17 gives an example of a POST HTTP request constructed using the previously mentioned classes.
post("/test_uri")
 .param("admin", "false")
 .accept(MediaType.APPLICATION_JSON)
 .content("{JSON_DATA}");
Listing 9-17

POST HTTP Request

The post method is part of the MockMvcRequestBuilders class and is used to create a POST request. The MockMvcRequestBuilders also provides additional methods such as get, delete, and put to create corresponding HTTP requests. The param method is part of the MockHttpServletRequestBuilder class and is used to add a parameter to the request. The MockHttpServletRequestBuilder provides additional methods such as accept, content, cookie, and header to add data and metadata to the request being constructed.

The perform method returns an org.springframework.test.web.servlet.ResultActions instance that can be used to apply assertions/expectations on the executed response. Listing 9-18 shows three assertions applied to the response of a sample POST request using ResultActions’s andExpect method. The status is a static method in org.springframework.test.web.servlet.result.MockMvcResultMatchers that allows you to apply assertions on response status. Its isOk method asserts that the status code is 200 (HTTPStatus.OK). Similarly, the content method in MockMvcResultMatchers provides methods to assert response body. Here we are asserting that the response content type is of type “application/json” and matches an expected string “JSON_DATA.
mockMvc.perform(post("/test_uri"))
        .andExpect(status().isOk())
        .andExpect(content().contentType(MediaType.APPLICATION_JSON))
        .andExpect(content().string("{JSON_DATA}"));
Listing 9-18

ResultActions

So far, we have looked at using MockMvc to perform requests and assert the response. Before we can use MockMvc, we need to initialize it. The MockMvcBuilders class provides the following two methods to build a MockMvc instance:
  • WebAppContextSetup—Builds a MockMvc instance using a fully initialized WebApplicationContext. The entire Spring configuration associated with the context is loaded before MockMvc instance is created. This technique is used for end-to-end testing.

  • StandaloneSetup—Builds a MockMvc without loading any Spring configuration. Only the basic MVC infrastructure is loaded for testing controllers. This technique is used for unit testing.

Unit Testing Using Spring MVC Test Framework

Now that we have reviewed the Spring MVC Test framework, let’s look at using it to test REST controllers. The PollControllerTest class in Listing 9-19 demonstrates testing the getPolls method . To the @ContextConfiguration annotation, we pass in a MockServletContext class instructing Spring to set up an empty WebApplicationContext. An empty WebApplicationContext allows us to instantiate and initialize the one controller that we want to test without loading up the entire application context. It also allows us to mock the dependencies that the controller requires.
package com.apress.unit;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mock.web.MockServletContext;
import org.springframework.test.web.servlet.MockMvc;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = QuickPollApplication.class)
@ContextConfiguration(classes = MockServletContext.class)
@WebAppConfiguration
public class PollControllerTest {
        @InjectMocks
        PollController pollController;
        @Mock
        private PollRepository pollRepository;
        private MockMvc mockMvc;
        @Before
        public void setUp() throws Exception {
                MockitoAnnotations.initMocks(this);
                mockMvc = standaloneSetup(pollController).build();
        }
        @Test
        public void testGetAllPolls() throws Exception {
                when(pollRepository.findAll()).thenReturn(new ArrayList<Poll>());
                mockMvc.perform(get("/v1/polls"))
                        .andExpect(status().isOk())
                        .andExpect(content().string("[]"));
        }
}
Listing 9-19

Unit Testing with Spring MVC Test

In this case, we want to test version 1 of our PollController API. So we declare a pollController property and annotate it with @InjectMocks. During runtime, Mockito sees the @InjectMocks annotation and will create an instance of the import com.apress.v1.controller.PollController.PollController. It then injects it with any mocks declared in the PollControllerTest class using constructor/field or setter injection. The only mock we have in the class is the PollRepository.

In the @Before annotated method, we use the MockMvcBuilders’s standaloneSetup() method to register the pollController instance. The standaloneSetup() automatically creates the minimum infrastructure required by the DispatcherServlet to serve requests associated with the registered controllers. The MockMvc instance built by standaloneSetup is stored in a class-level variable and made available to tests.

In the testGetAllPolls method , we use Mockito to program the PollRepository mock’s behavior. Then we perform a GET request on the /v1/polls URI and use the status and content assertions to ensure that an empty JSON array is returned. This is the biggest difference from the version that we saw in Listing 9-16. There we were testing the result of a Java method invocation. Here we are testing the HTTP response that the API generates.

Integration Testing REST Controllers

In the previous section, we looked at unit testing a controller and its associated configuration. However, this testing is limited to a web layer. There are times when we want to test all of the layers of an application from controllers to the persistent store. In the past, writing such tests required launching the application in an embedded Tomcat or Jetty server and use a framework such as HtmlUnit or RestTemplate to trigger HTTP requests. Depending on an external servlet container can be cumbersome and often slows down testing.

The Spring MVC Test framework provides a lightweight, out-of-the-container alternative for integration testing MVC applications. In this approach, the entire Spring application context along with the DispatcherServlet and associated MVC infrastructure gets loaded. A mocked MVC container is made available to receive and execute HTTP requests. We interact with real controllers and these controllers work with real collaborators. To speed up integration testing, complex services are sometimes mocked. Additionally, the context is usually configured such that the DAO/repository layer interacts with an in-memory database.

This approach is similar to the approach we used for unit testing controllers, except for these three differences:
  • The entire Spring context gets loaded as opposed to an empty context in the unit testing case.

  • All REST endpoints are available as opposed to the ones configured via standaloneSetup.

  • Tests are performed using real collaborators against in-memory database as opposed to mocking dependency’s behavior.

An integration test for the PollController’s getAllPolls method is shown in Listing 9-20. The PollControllerIT class is similar to the PollControllerTest that we looked at earlier. A fully configured instance of WebApplicationContext is injected into the test. In the @Before method, we use this WebApplicationContext instance to build a MockMvc instance using MockMvcBuilders’s webAppContextSetup.
package com.apress.it;
import static org.hamcrest.Matchers.hasSize;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.context.WebApplicationContext;
import com.apress.QuickPollApplication;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = QuickPollApplication.class)
@WebAppConfiguration
public class PollControllerIT {
        @Inject
        private WebApplicationContext webApplicationContext;
        private MockMvc mockMvc;
        @Before
        public void setup() {
                  mockMvc = webAppContextSetup(webApplicationContext).build();
        }
        @Test
        public void testGetAllPolls() throws Exception {
                mockMvc.perform(get("/v1/polls"))
                        .andExpect(status().isOk())
                        .andExpect(jsonPath("$", hasSize(20)));
        }
}
Listing 9-20

Integration Testing with Spring MVC Test

The testGetAllPolls method implementation uses the MockMvc instance to perform a GET request on the /v1/polls endpoint. We use two assertions to ensure that the result is what we expect:
  • The isOK assertion ensures that we get a status code 200.

  • The JsonPath method allows us to write assertions against response body using JsonPath expression. The JsonPath (http://goessner.net/articles/JsonPath/) provides a convenient way to extract parts of a JSON document. To put it simply, JsonPath is to JSON is what XPath is to XML.

In our test case, we use the Hamcrest’s hasSize matcher to assert that the retuned JSON contains 20 polls. The import.sql script used to populate the in-memory database contains 20 poll entries. Hence, our assertion uses the magic number 20 for comparison.

Summary

Spring provides powerful template and utility classes that simplify REST client development. In this chapter, we reviewed RestTemplate and used it to perform client operations such as GET, POST, PUT, and DELETE on resources. We also reviewed the Spring MVC Test framework and its core classes. Finally, we used the test framework to simplify unit and integration test creation.

..................Content has been hidden....................

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