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:
Operator | describe |
$ | 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)