Spring Boot 2 Reality: mock Tests Your web Applications

Keywords: Programming Spring Java Junit JSON

1. outline

Software testing is a guarantee of application software quality. java developers often ignore interface unit testing when developing interfaces. As a java development, if Mock unit tests are available, then your bug count will be greatly reduced. spring provides test test test module, so now Little Pang takes you to play with the Mock unit test under springboot. We will do battle on the unit test of controller and service.

2. Dependency Introduction

​​

       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

Introduce dependencies as above and scope is test. This dependency provides the following class libraries

  • JUnit 4: Currently the most powerful java application unit testing framework
  • Spring Test & Spring Boot Test: Spring Boot Integrated Test Support.
  • AssertJ: A java assertion library that provides test assertion support.
  • Hamcrest: Object matching assertions and constraint components.
  • Mockito: A well-known Java mock simulation framework.
  • JSONassert: JSON Assertion Library.
  • JsonPath: JSON XPath Operational Class Library.

These are all class libraries that are frequently encountered in unit testing. You'd better study it when you have time.

3. Configuring the test environment

A Spring Book application is a Spring Application Context, and general testing does not go beyond that range. The test framework provides a @SpringBootTest annotation to provide SpringBoot unit test environment support. If you use JUnit 4, don't forget to add @RunWith (Spring Runner. class) to the test class, JUnit 5 won't be needed. By default, @SpringBootTest does not start the server. You can use its webEnvironment attribute to further optimize the way the test runs, webEnvironment related instructions:

  • MOCK (default): Load Web Application Context and provide a simulated Web environment. The embedded server will not be started under this option. If there is no Web environment on the classpath, a regular non-Web ApplicationContext will be created. You can work with @AutoConfigureMockMvc or @AutoConfigureWebTestClient simulated Web applications.
  • RANDOM_PORT: Load the WebServer Application Context and provide a real Web environment with a random web container port enabled.
  • DEFINED_PORT: Loading the WebServer Application Context and providing a real Web environment and RANDOM_PORT is different from enabling the SpringBook application ports you activate, which are usually declared in the application.yml configuration file.
  • NONE: Load an Application Context through Spring Application. But no Web environment (whether Mock or others) is provided.

Note: If your test is annotated with @Transactional, by default each test method will roll back the transaction after execution. But when your web environment is set to RANDOM_PORT or DEFINED_PORT, which implicitly provides a real servlet web environment, it will not roll back. This is particularly important. Make sure that dirty data is not written in production release tests.

4. Write test classes to test your api

To get back to the point, we first wrote a BookService as a Service layer. ​​

package cn.felord.mockspringboot.service;

import cn.felord.mockspringboot.entity.Book;

/**
 * The interface Book service.
 *
 * @author Dax
 * @since 14 :54  2019-07-23
 */
public interface BookService {

    /**
     * Query by title book.
     *
     * @param title the title
     * @return the book
     */
    Book queryByTitle(String title);

}

The implementation classes are as follows. In order to make it simple and clear that there is no test persistence layer, Spring transaction annotation @Transactional support is needed to roll back after test if the persistence layer needs to be tested.

package cn.felord.mockspringboot.service.impl;

import cn.felord.mockspringboot.entity.Book;
import cn.felord.mockspringboot.service.BookService;
import org.springframework.stereotype.Service;

import java.time.LocalDate;

/**
 * @author Dax
 * @since 14:55  2019-07-23
 */
@Service
public class BookServiceImpl implements BookService {


    @Override
    public Book queryByTitle(String title) {
        Book book = new Book();
        book.setAuthor("dax");
        book.setPrice(78.56);
        book.setReleaseTime(LocalDate.of(2018, 3, 22));
        book.setTitle(title);
        return book;
    }
}

​​

The controller layer is as follows:

​​

package cn.felord.mockspringboot.api;

import cn.felord.mockspringboot.entity.Book;
import cn.felord.mockspringboot.service.BookService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * @author Dax
 * @since 10:24  2019-07-23
 */
@RestController
@RequestMapping("/book")
public class BookApi {
    @Resource
    private BookService bookService;

    @GetMapping("/get")
    public Book getBook(String title) {
        return bookService.queryByTitle(title);
    }

}

We write our own test classes in the corresponding classpath under the unit test package test of Spring Boot maven project

​​

 package cn.felord.mockspringboot;
 
 import cn.felord.mockspringboot.entity.Book;
 import cn.felord.mockspringboot.service.BookService;
 import org.assertj.core.api.Assertions;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.BDDMockito;
 import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.test.context.junit4.SpringRunner;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
 import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
 import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
 
 import javax.annotation.Resource;
 import java.time.LocalDate;
 
 /**
  * The type Mock springboot application tests.
  */
 @RunWith(SpringRunner.class)
 @SpringBootTest
 @AutoConfigureMockMvc
 public class MockSpringbootApplicationTests {
     @Resource
     private MockMvc mockMvc;
     @MockBean
     private BookService bookService;
 
     @Test
     public void bookApiTest() throws Exception {
         String title = "java learning";
         // mockbean starts simulation
         bookServiceMockBean(title);
         // mockbean simulation
         String expect = "{\"title\":\"java learning\",\"author\":\"dax\",\"price\":78.56,\"releaseTime\":\"2018-03-22\"}";
         mockMvc.perform(MockMvcRequestBuilders.get("/book/get")
                 .param("title", title))
                 .andExpect(MockMvcResultMatchers.content()
                         .json(expect))
                 .andDo(MockMvcResultHandlers.print());
         // mockbean reset
     }
   
     @Test
     public void bookServiceTest() {
 
         String title = "java learning";
         bookServiceMockBean(title);
 
 
         Assertions.assertThat(bookService.queryByTitle("ss").getTitle()).isEqualTo(title);
 
     }
       /**
        * Mock piling
        * @param title the title
        */   
     private void bookServiceMockBean(String title) {
 
         Book book = new Book();
         book.setAuthor("dax");
         book.setPrice(78.56);
         book.setReleaseTime(LocalDate.of(2018, 3, 22));
         book.setTitle(title);
 
         BDDMockito.given(bookService.queryByTitle(title)).willReturn(book);
     }
  }

Needless to say, the first two annotations of the test class @AutoConfigureMockMvc may be strange to you. This is used to turn on the automated configuration for Mock Mvc testing.

Then we write a test method, bookApiTest(), to test the BookApi getBook (String title) interface. ​​

The logic is that MockMvc executes a simulated get request and expects the result to be an expect Json string and prints out the corresponding object (identified in Figure 1 below). Once the request does not throw a java.lang.AssertionError error, the expected value (Expected) is printed with the actual value (identified in Figure 2 below). If it is the same as expected, only Figure 1 below will appear.

​​

5. Test piling

There is a very common situation, in the development of other services you call may not be developed, such as you have a short message sending interface is still in the process of SMS interface procedures, but you also need the short message interface to test. You can build an abstract interface implementation with @MockBean. Take BookService as an example, if its implementation class logic has not been determined, we can simulate the logic of the bean by specifying its input and corresponding return value, or select a routing operation according to a situation (if the input is A, the result is B, if it is C, D). This simulation is also known as test piling. We'll use Mockito here.

The test scenario is described as follows:

  1. Specifies the return value of the piling object
  2. Determine the number of times a method of a pile-driving object is called
  3. Specify the object of piling to throw a particular exception

There are generally the following combinations:

  • do/when: Includes doThrow(... .when(...) )/ do Return (... .when(...) )/ Do Answer (... .when(...) )
  • Give / will: Includes given(... ) Will Return (... )/ Give (...) ) Will Answer (... )
  • when/then: Includes when(... ) The nReturn (... / when(... ) The nAnswer (... )

Answer is just to solve the problem that if the entry is A, the result will be B, and if it is C, the routing operation will be D. Next, let's do it. It's basically the same as it was at the beginning. It's just changed to @MockBean.

​​

Then, we use Mockito to write void bookService MockBean (String title) to simulate the above BookService Impl implementation class. However, the simulated beans are automatically reset after each test. And it cannot be used to simulate the behavior of beans running during application context refresh.

​​

This method can then be tested by injecting it into the controller test method.

​​

6. other

The built-in assertj is also a common assertion, and the api is very friendly. Here's a brief demonstration through bookService Test ().

​​

7. summary

In this article, we implemented some simple Spring Boot enabled integration testing. The construction of test environment and the compilation of test code have been operated in battle, which can basically meet the needs of daily development and testing. I believe you can learn a lot from this article.

Relevant explanatory code can be obtained from gitee Obtain.

Or through me Personal blog Get more dry goods to share in time.

Focus on Public Number: Felordcn Gets More Information

Personal blog: https://felord.cn

Posted by zartzar on Fri, 11 Oct 2019 21:01:03 -0700