Using Mockito in Spring Boot projects

Keywords: Java Spring Junit JSON Database

This article first appeared on personal website: Using Mockito in Spring Boot projects

Spring Boot can work with most popular testing frameworks: create unit tests through Spring JUnit; generate test data to initialize the database for testing; Spring Boot can work with BDD (behavior driven development) tools, Cucumber and Spock to test applications.

In software development, we will write a lot of code, but in another six months (or even more than a year), do you know how your code works? The maintainability of the system can be guaranteed by testing (unit testing, integration testing, interface testing). When we modify some codes, we can check whether new bug s are introduced by regression testing. In general, testing makes the system no longer a black box, allowing developers to confirm that the system is available.

In web applications, there are two ways to test the Controller layer: (1) send http request; (2) simulate http request object. The first method needs to configure the regression environment to calculate the coverage rate by modifying the code statistics strategy; the second method is a more formal idea, but it is not used much in the projects I have experienced at present. Today, I summarize how to test the code of Controller layer with Mock object.

In previous articles, we used bookpub as an example, and today is no exception. We are ready to test whether the RESTful interface it provides can return correct response data. This kind of test is different from unit test. It needs to initialize the complete application context, weave all spring bean s and have test data in the database. Generally speaking, this kind of test is called integration test or interface test.

actual combat

The new Spring Boot project created through sping.io provides an empty test file, BookPubApplicationTest.java, which contains:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = BookPubApplication.class)
public class BookPubApplicationTests {
   @Test
   public void contextLoads() {
   }
}
  • Add spring boot starter test dependency and jsonPath dependency in pom file
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
   <scope>test</scope>
</dependency>
<dependency>
   <groupId>com.jayway.jsonpath</groupId>
   <artifactId>json-path</artifactId>
</dependency>
  • Add test cases to BookPubApplicationTest
package com.test.bookpub;

import com.test.bookpub.domain.Book;
import com.test.bookpub.repository.BookRepository;
import org.junit.Before;import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.TestRestTemplate;
import org.springframework.boot.test.WebIntegrationTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.context.WebApplicationContext;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.hamcrest.Matchers.containsString;
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.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = BookPubApplication.class)
@WebIntegrationTest("server.port:0")
public class BookPubApplicationTests {
    @Autowired
    private WebApplicationContext context;
    @Autowired
    private BookRepository bookRepository;
    @Value("${local.server.port}")
    private int port;

    private MockMvc mockMvc;
    private RestTemplate restTemplate = new TestRestTemplate();

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

   @Test
   public void contextLoads() {
        assertEquals(1, bookRepository.count());
   }

    @Test
    public void webappBookIsbnApi() {
        Book book = restTemplate.getForObject("http://localhost:" + port +"/books/9876-5432-1111", Book.class);
        assertNotNull(book);
        assertEquals("Chinese test", book.getPublisher().getName());
    }

    @Test
    public void webappPublisherApi() throws Exception {
        //The MockHttpServletRequestBuilder.accept method is to set the content type recognized by the client
        //MockHttpServletRequestBuilder.contentType, set the content type field in the request header to indicate the content type of the request body
        mockMvc.perform(get("/publishers/1")
                .accept(MediaType.APPLICATION_JSON_UTF8))

                .andExpect(status().isOk()) 
               .andExpect(content().string(containsString("Chinese test")))
                .andExpect(jsonPath("$.name").value("Chinese test"));
    }
}
  • Code coverage of spring boot project
    Using cobertura, refer to the project's github address: spring boot template
# To create test coverage reports (in target/site/cobertura)
mvn clean cobertura:cobertura test


Analysis

First, analyze the annotations used in BookPubApplicationTests class:

  • @Run with (SpringJUnit4ClassRunner. Class), which is the annotation of JUnit. Use this annotation to let SpringJUnit4ClassRunner provide the Spring test context.
  • @Spring application configuration (classes = bookpubapplication. Class). This is the Spring Boot annotation. In order to conduct integration testing, you need to load and configure the spring application context through this annotation. This is a meta annotation, which contains the annotation @ contextconfiguration (loader = SpringApplicationContextLoader. Class). The test framework uses this annotation to create applications using the spring applicationcontextloader loader loader of the Spring Boot framework.
  • @Web integration test ("server.port:0"), this annotation indicates that the current test is integration test, so you need to initialize the full context and start the application. This annotation usually appears with @ SpringApplicationConfiguration. server.port:0 means to let Spring Boot start Tomcat service on a random port, and then the program obtains the port number through @ Value("${local.server.port}") in the test and assigns it to the port variable. When running a test program on Jenkins or other continuous integration servers, this ability to randomly acquire ports can provide the parallelism of the test program.

After understanding the annotations of the test class, take a look inside the test class. Because this is a test of Spring Boot, we can weave in any Spring managed object through @ Autowired annotation, or set the Value of the specified environment variable through @ Value. In the current test class, we define the WebApplicationContext and BookRepository objects.

Each Test case is decorated with @ Test annotation. In the first Test case - contextLoads() method, I just need to confirm that the BookRepository connection has been established and that the corresponding Test data has been included in the database.

The second test case is used to test the RESTful URL we provide -- query a book through ISBN, that is "/ books/{isbn}". In this test case, we use the TestRestTemplate object to initiate a RESTful request.

The third test case shows how to implement similar functions with the second test through MockMvc objects. The Spring test framework provides MockMvc objects, which can be MVC tested without client-server requests. Controller requests can be executed completely on the server side, just like starting the test server.

The test environment needs to be established Before the test starts, and the setup method is decorated with @ Before. Through the MockMvcBuilders tool, using the WebApplicationContext object as a parameter, create a MockMvc object.

MockMvc object provides a set of tool functions to perform assert judgment, which are all for the judgment of web request. This set of tools is used by chaining function calls, allowing programmers to link multiple test cases together and make multiple judgments. In this example, we use the following utility functions:

  • perform(get(...)) creates a web request. In our third use case, GET requests are executed through mockmvrequestbuilder.
  • andExpect(...) can be called many times after the function of perform(...) is called to judge multiple conditions. The parameter type of this function is the ResultMatcher interface. In the class of MockMvcResultMatchers, there are many utility functions that return the ResultMatcher interface. This function enables to detect multiple aspects of the same web request, including HTTP response status code, content type of response, value stored in the session, content of check redirection, model or header, etc. Here we need to detect the response data in JSON format through the third-party library JSON path: check that the JSON data contains the correct element type and corresponding value, for example, jsonPath("$.name").value("Chinese test") is used to check that there is a node named name in the root directory, and the corresponding value of the node is "Chinese test".

A character scrambling problem

  • Problem Description: through the repository established by spring boot starter data rest, the extracted Chinese characters are garbled.
  • Analysis: there's no problem in using postman and httpie to verify. It shows that the test case of Mockmvc is not written correctly. You should actively set how the client parses the HTTP response, and use get.accept method to set the content type recognized by the client. The modified test case is as follows:
@Test
public void webappPublisherApi() throws Exception {
    //The MockHttpServletRequestBuilder.accept method is to set the content type recognized by the client
    //MockHttpServletRequestBuilder.contentType, set the content type field in the request header to indicate the content type of the request body
    mockMvc.perform(get("/publishers/1")
            .accept(MediaType.APPLICATION_JSON_UTF8))

            .andExpect(status().isOk())
            .andExpect(content().string(containsString("Chinese test")))
            .andExpect(jsonPath("$.name").value("Chinese test"));
}

Reference material

  1. Integration test of Restful API based on spring WS
  2. The little things J2EE needs to understand - illustrating HTTP protocol
  3. Integration Testing a Spring Boot Application
  4. spring boot project template

Spring Boot 1.x Series

  1. Automatic configuration and command line runner of Spring Boot
  2. Understand the automatic configuration of Spring Boot
  3. The use of @ PropertySource annotation of Spring Boot in Redis integration
  4. How to customize HTTP message converter in Spring Boot project
  5. Spring Boot integrates Mongodb to provide Restful interface
  6. scope of bean s in Spring
  7. Using event dispatcher pattern in Spring Boot project
  8. Error handling practice when Spring Boot provides RESTful interface
  9. Spring Boot: customize your own starter
  10. How the Spring Boot project supports both HTTP and HTTPS protocols
  11. How to set auto configuration annotation for custom Spring Boot starter

This number focuses on back-end technology, JVM troubleshooting and optimization, Java interview questions, personal growth and self-management and other topics, providing readers with the work and growth experience of front-line developers, and looking forward to your harvest here.

Posted by WendyLady on Sun, 27 Oct 2019 03:25:22 -0700