springboot+junit5+surefire test report

1. Framework:

springboot + mybatis + mysql + junit5

2. Project code

2.1 pom file

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.4</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>demo</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!-- springboot frame -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>

        <!-- mybatis -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.4</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.5</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.5</version>
        </dependency>

        <!--  mysql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
            <version>8.0.15</version>
        </dependency>

        <!-- jsbc -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.3.6</version>
        </dependency>

        <!-- affair -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
        </dependency>

        <!-- springboot Self contained test package -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>junit</groupId>
                    <artifactId>junit</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <!--springboot Version 2.2 For the above junit5,Without processing,The code will be used automatically junit4,Avoid version conflicts mvn test,Therefore, it shall be treated separately-->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>


    <build>
        <plugins>
            <!--maven plug-in unit-->
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>

            <!--Command: mvn test generate xml txt Test report( maven-surefire-plugin mvn Plug ins are used by default,(no configuration required) -->
            <!--Command: mvn surefire-report:report Generate test report html -->
            <!--junit5 Plug in below version Greater than 2.22.0,Otherwise, it cannot be executed test-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-report-plugin</artifactId>
                <version>2.22.2</version>
                <configuration>
                    <showSuccess>false</showSuccess>

                </configuration>
            </plugin>

            <!--Command: mvn cobertura:cobertura Generate test coverage report html -->
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>cobertura-maven-plugin</artifactId>
                <version>2.5.1</version>
            </plugin>
        </plugins>
    </build>
</project>

2.2 spring configuration file

# Tomcat
server:
  port: 8080

# spring configuration
spring:
  profiles:
    # Environment configuration
    active: dev
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/product?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8
    username: root
    password: 123456

# mybatis configuration
mybatis:
  #Search for the specified package alias
  typeAliasesPackage: com.example.demo.entity
  #Configure mapper scanning and find all mapper.xml mapping files
  mapperLocations: classpath:mapper/*.xml
  configLocations: classpath:mybatis-config.xml

2.3 Controller layer

package com.example.demo.controller;

import com.example.demo.entity.Product;
import com.example.demo.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * @Author mxy
 * @Date 2021/10/13
 * @Desc controller
 */
@RestController
@RequestMapping("/product")
public class ProductController {

    @Autowired
    private ProductService productService;

    @GetMapping()
    public List<Product> list(Product product) {
        return productService.queryAll(product);
    }

    @PutMapping()
    public Boolean update(@RequestBody Product product) {
        return productService.update(product);
    }

    @DeleteMapping("/{id}")
    public Boolean deleteById(@PathVariable String id) {
        return productService.deleteById(id);
    }

    @PostMapping()
    public Product save(@RequestBody Product product) {
        return productService.insert(product);
    }
}

2.4 entity class

package com.example.demo.entity;

/**
 * @Author mxy
 * @Date 2021/9/1
 * @Desc entity
 */
public class Product {
    /** Object ID */
    private String id;
    /** Product name */
    private String name;
    /** Product quotation */
    private Double price;
    /** Enable */
    private Integer status;

    public Product() {
    }

    public Product(String id, String name, Double price, Integer status) {
        this.id = id;
        this.name = name;
        this.price = price;
        this.status = status;
    }

    public String getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

}

2.5 Service class

package com.example.demo.service;


import com.example.demo.entity.Product;

import java.util.List;


/**
 * @Author mxy
 * @Date 2021/10/13
 * @Desc Service class
 */
public interface ProductService {

    /**
     * Query all data
     *
     * @param product Screening conditions
     * @return
     */
    List<Product> queryAll(Product product);

    /**
     * Query data details by ID
     *
     * @param id
     * @return
     */
    Product queryById(String id);

    /**
     * Void a data according to ID (non physical deletion)
     *
     * @param id Object ID
     * @return
     */
    Boolean deleteById(String id);

    /**
     * Save data
     *
     * @param product entity
     * @return
     */
    Product insert(Product product);

    /**
     * Update a piece of data
     *
     * @param product entity
     * @return
     */
    Boolean update(Product product);


}

two point six   Service implementation class

package com.example.demo.service.impl;

import com.example.demo.entity.Product;
import com.example.demo.mapper.ProductMapper;
import com.example.demo.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.UUID;


/**
 * @Author mxy
 * @Date 2021/10/13
 * @Desc Service implementation class
 */
@Service
public class ProductServiceImpl implements ProductService {

    @Autowired
    private ProductMapper productMapper;

    @Override
    public List<Product> queryAll(Product product) {
        return productMapper.selectList(product);
    }

    @Override
    public Product queryById(String id) {
        return productMapper.selectById(id);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean deleteById(String id) {
        Product product = new Product();
        product.setStatus(0);
        product.setId(id);
        return productMapper.updateById(product) > 0;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Product insert(Product product) {
        product.setId(UUID.randomUUID().toString());
        product.setStatus(1);
        productMapper.insert(product);
        return product;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean update(Product product) {
        return productMapper.updateById(product) > 0;
    }
}

2.7 mapper interface

package com.example.demo.mapper;

import com.example.demo.entity.Product;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

/**
 * Mapper Interface
 *
 * @author mxy
 * @since 2021-09-30
 */
@Mapper
public interface ProductMapper {

    List<Product> selectList(Product product);

    Product selectById(String id);

    int updateById(Product product);

    int insert(Product product);

}

two point eight   Startup class

package com.example.demo;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @Author mxy
 * @Date 2021/10/13
 * @Desc Startup class
 */
@SpringBootApplication
@MapperScan(basePackages = {"com.example.demo.mapper"})
public class DemoApplication {

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

}

3 test code

three point one   Controller integration test

package com.example.demo.controller;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.junit.jupiter.SpringExtension;
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 org.springframework.transaction.annotation.Transactional;

import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

/**
 * @Author mxy
 * @Date 2021/10/13
 * @Desc integration testing 
 */
@ExtendWith(SpringExtension.class)
@SpringBootTest
@AutoConfigureMockMvc
public class ProductControllerTest {
    private static final String BASE_URL = "/product";

    @Autowired
    private MockMvc mockMvc;

    @Test
    @Transactional
    @Rollback()
    public void save() throws Exception {
        String json = "{\"name\":\"testOne\", \"price\":100}";
        mockMvc.perform(MockMvcRequestBuilders
                        .post(BASE_URL)
                        .accept(MediaType.APPLICATION_JSON_VALUE)
                        .contentType(MediaType.APPLICATION_JSON_VALUE)
                        .content(json.getBytes()))
                .andExpect(status().isOk())
                .andExpect(MockMvcResultMatchers.jsonPath("$.name").value("testOne"))
                .andDo(print())
                .andReturn(); //Return MvcResult
    }

    /*
     * 1,mockMvc.perform Execute a request.
     * 2,MockMvcRequestBuilders.get("XXX")Construct a request.
     * 3,ResultActions.param Add request value
     * 4,ResultActions.accept(MediaType.TEXT_HTML_VALUE))Set return type
     * 5,ResultActions.andExpect Add an assertion after execution is complete.
     * 6,ResultActions.andDo Add a result handler to indicate what to do with the result
     *   In this case, use MockMvcResultHandlers.print() to output the entire response result information.
     * 7,ResultActions.andReturn Indicates that the corresponding result is returned after execution, and the next operation can be carried out according to the result.
     */
    @Test
    @Transactional
    @Rollback()
    public void list() throws Exception {
        //Here, the list interface is tested. andExpect indicates the expected return value. If it is the expected value, an exception will be reported
        mockMvc.perform(MockMvcRequestBuilders
                                .get(BASE_URL)
                                .accept(MediaType.APPLICATION_JSON_VALUE)
                                .param("name", "testOne")
//                        .header("Authorization", "Bearer ********-****-****-****-************")
                )
                .andExpect(status().isOk())
//                . andExpect(MockMvcResultMatchers.content().string("success");
                .andDo(print());
//                .andReturn();
    }

    @Test
    @Transactional
    @Rollback()
    public void update() throws Exception {
        String json = "{\"id\":\"1dd85201-3d91-4b8d-94d1-42e8d33a71b4\",\"name\":\"testOne-update\"}";
        mockMvc.perform(MockMvcRequestBuilders
                        .post(BASE_URL)
                        .accept(MediaType.APPLICATION_JSON_VALUE)
                        .contentType(MediaType.APPLICATION_JSON_VALUE)
                        .content(json.getBytes()))
                .andExpect(status().isOk())
                .andDo(print());
    }

    @Test
    @Transactional
    @Rollback()
    public void deleteById() throws Exception {
        mockMvc.perform(MockMvcRequestBuilders
                        .delete(BASE_URL+"/{id}", "1dd85201-3d91-4b8d-94d1-42e8d33a71b4")
                        .accept(MediaType.APPLICATION_JSON_VALUE))
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andDo(MockMvcResultHandlers.print());
    }
}

3.2 service implementation class unit test

package com.example.demo.service.impl;

import com.example.demo.entity.Product;
import com.example.demo.mapper.ProductMapper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import static org.junit.jupiter.api.Assertions.*;

/**
 * @Author huangqianqian
 * @Date 2021/10/13
 * @Desc service unit testing 
 * All data is not mock and context independent
 */
@ExtendWith(MockitoExtension.class)
class ProductServiceImplTest {

    @Mock
    private ProductMapper productMapper;

    private List<Product> getProductList() {
        List<Product> mockList = new ArrayList<Product>();

        Product mockProduct = getProduct();
        Product mockProduct2 = new Product();
        mockProduct2.setId("222");
        mockProduct2.setName("testTwo");
        mockProduct2.setPrice(100.0);

        mockList.add(mockProduct);
        mockList.add(mockProduct2);
        return mockList;
    }

    private Product getProduct() {
        Product mockProduct = new Product();
        mockProduct.setId("111");
        mockProduct.setName("testOne");
        mockProduct.setPrice(100.0);
        return mockProduct;
    }

    @Test
    void queryAll() {

        // mock value
        Product product = new Product();
        List<Product> mockProductList = getProductList();

        // mock interface request return value
        Mockito.when(productMapper.selectList(product)).thenReturn(mockProductList);

        // Call the interface, and the return value adopts the mock request above
        List<Product> productList = productMapper.selectList(product);

        // Assert whether the data is as expected
        Assertions.assertEquals(mockProductList, productList);
    }

    @Test
    void queryById() {
        String id = "111";
        Product mockProduct = getProduct();

        Mockito.when(productMapper.selectById(id)).thenReturn(mockProduct);
        Product result = productMapper.selectById(id);

        Assertions.assertEquals(mockProduct, result);
    }

    @Test
    void deleteById() {
        String id = "111";
        Product product = new Product();
        product.setStatus(0);
        product.setId(id);

        Mockito.when(productMapper.updateById(product)).thenReturn(1);
        int result = productMapper.updateById(product);
        Assertions.assertEquals(1, result);
    }

    @Test
    void insert() {
        Product mockProduct = getProduct();
        mockProduct.setId(UUID.randomUUID().toString());
        mockProduct.setStatus(1);

        Mockito.when(productMapper.insert(mockProduct)).thenReturn(1);
        int result = productMapper.insert(mockProduct);
        Assertions.assertEquals(1, result);
    }

    @Test
    void update() {
        Product mockProduct = getProduct();

        Mockito.when(productMapper.updateById(mockProduct)).thenReturn(1);
        int result = productMapper.updateById(mockProduct);
        Assertions.assertEquals(1, result);
    }
}

3.3 service implementation class integration test

package com.example.demo.service.impl;

import com.example.demo.entity.Product;
import com.example.demo.service.ProductService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import java.util.List;

/**
 * @Author huangqianqian
 * @Date 2021/10/13
 * @Desc service integration testing 
 * Non mock, context dependent
 */
@ExtendWith(SpringExtension.class)
@SpringBootTest
class ProductServiceImplIntegrationTest {

    @Autowired
    private ProductService productService;

    private static final String id = "1dd85201-3d91-4b8d-94d1-42e8d33a71b4";

    private Product getProduct() {
        Product mockProduct = new Product();
        mockProduct.setId("1dd85201-3d91-4b8d-94d1-42e8d33a71b4");
        mockProduct.setName("testOne");
        mockProduct.setPrice(100.0);
        mockProduct.setStatus(1);
        return mockProduct;
    }

    @Test
    void queryAll() {
        List<Product> result = productService.queryAll(new Product());
        Assertions.assertNotNull(result);
    }

    @Test
    void queryById() {
        Product product = productService.queryById(id);
        Assertions.assertNotNull(product);
    }

    @Test
    void deleteById() {
        boolean result = productService.deleteById(id);
        Assertions.assertTrue(result);
    }

    @Test
    void insert() {
        Product product = getProduct();
        Product result = productService.insert(product);
        Assertions.assertNotNull(result);
        Assertions.assertEquals(product.getName(), result.getName());
    }

    @Test
    void update() {
        Product product = getProduct();
        boolean result = productService.update(product);
        Assertions.assertTrue(result);
    }
}

 

4 @SpringBootTest

@After the SpringBootTest annotation is used, there will be context switching to start spring. If it is a unit test, use mock data, which does not depend on the context, and you can not add this annotation.

5 @AutoConfigureMockMvc,MockMvc

This annotation is used in conjunction with MockMvc to simulate requests.

5.1 basic flow chart

5.1 Grammar (write it briefly):

Mockmvc.perform (mockmvcrequebuilders. Request method ("url"). contentType (MediaType.APPLICATION_FORM_URLENCODED).param("key", "value");

five point two   MockMvc description

  • The main entry point for server-side spring MVC testing.
  • The MockMVCBuilder is built by the static method of the MockMVCBuilder builder, and the MockMvc is constructed by the MockMVCBuilder.
  • Core method: perform(RequestBuilder rb). When a RequestBuilder request is executed, the spring MVC process will be automatically executed and mapped to the corresponding controller for processing. The return value of this method is a ResultActions.

five point three   Basic composition and process

Several important components of MockMvc are as follows:

5.3.1 MockMVCBuilder

  • MockMVCBuilder is a constructor that uses the constructor pattern to construct MockMvc.
  • There are two main implementations: StandaloneMockMvcBuilder and DefaultMockMvcBuilder.
  • You can directly use the static factory MockMvcBuilders to create it without directly using the above two implementation classes.

5.3.2 MockMVCBuilders

  • Responsible for creating the MockMVCBuilder object.
  • There are two ways to create
    • Standalone setup (object... Controllers): specify a set of controllers through parameters, so you don't need to get them from the context.
    • webAppContextSetup(WebApplicationContext wac): specify WebApplicationContext, and the corresponding controller will be obtained from the context and the corresponding MockMvc will be obtained

5.3.3 MockMvcRequestBuilders

  • To create a request, you can set all the information contained in the basic http request, such as parameters, header information, encoding, Cookies, etc.
  • It mainly has two subclasses: MockHttpServletRequestBuilder and MockMultipartHttpServletRequestBuilder (for file upload), that is, it is used to request all data required by Mock client.

5.3.4 MockMvc

The client, the main portal, executes the request.

5.3.5 ResultActions

Results and actions: the results returned after MockMvc sends the request constructed by MockMvcRequestBuilders. On this basis, you can add some actions for the results. They include:

  • andExpect: add a ResultMatcher verification rule to verify whether the result is correct after the controller is executed.
  • andDo: add a ResultHandler result processor, such as printing results to the console during debugging. The common method is print()
  • andReturn: finally, return the corresponding MvcResult; then conduct user-defined verification / asynchronous processing in the next step. Among them, the types returned by andExpect and andDo are thrown as ResultActions, so you can add multiple actions in a chain way.
  • MockMvcResultMatchers
    • Used to match the * * result verification after the request is executed.
    • If the matching fails, the corresponding exception will be thrown.
    • Contains many validation API methods.
  • MockMvcResultHandlers
    • Result processor, indicating what to do with the result.
    • In this case, use MockMvcResultHandlers.print() to output the entire response result information.

5.4 JsonPath

JsonPath is a Java DSL for reading JSON documents.,
MockMvc introduces it to andExpect to read and compare json results. Read the document through an expression and pass in the comparison method for judgment.
It can be written as

$.store.book[0].title

or

$['store']['book'][0]['title']

The syntax of the expression for JsonPath is similar to jquery:

Operatordescribe
$The root element to query. All expression start elements.
@Query nodes according to filter expressions
*Wildcard. Can be used wherever a name or number is needed.
..Deep scan. Can be used anywhere you need a name.
.<name>Get node
['<name>' (, '<name>')]Get child node $['store']['book'] by name
[<number> (, <number>)]Get child node $['store'][0] according to index
[start:end]Gets the list of nodes from start to end
[?(<expression>)]Filter expression. The expression must evaluate to a Boolean value.

6 references

mockMvc tutorial - Code farming tutorial (manongjc.com)

SpringBoot MockMvc Unit Testing Introduction - brief book (jianshu.com)

Testing with Spring Boot 2.4 and JUnit 5 - HowToDoInJava

Tags: Java Junit Maven Spring Boot unit testing

Posted on Wed, 13 Oct 2021 15:19:39 -0400 by RyanW