• Spring Boot Mock


    Spring MockMvc

    今天来学习下如何使用Spring Mvc来对controller定义的Restful API进行集成测试。MockMVC 类是Spring test 框架的一部分,因此不需要额外引入单独的Maven依赖。使用Spring MockMvc有以下优点

    • 使开发人员摆脱第三方工具的依赖,如Postman、Apipost等
    • 微服务架构,团队之间的配合协调并不一致。如下单流程测试,需要订单微服务提供接口做全流程测试,但是订单接口尚未准备好,这时可以使用Mock功能进行模拟测试

    Maven依赖

    首先,在pom文件中添加以下依赖

    <dependency>
      <groupId>org.springframework.bootgroupId>
      <artifactId>spring-boot-starter-testartifactId>
      <scope>testscope>
    dependency>
    <dependency>
    			<groupId>org.mockitogroupId>
    			<artifactId>mockito-coreartifactId>
      		<version>4.8.1version>
    		dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    Mockito 基本使用

    基础代码

    为了熟悉Mockio的各种API,先自定义一个基础的类,在这个类的基础上实现各种mock操作。

    import java.util.AbstractList;
    public class MyList extends AbstractList<String> {
        @Override
        public String get(int index) {
            //注意 get方法默认返回给 null,方便后续mock
            return null;
        }
        @Override
        public int size() {
            return 1;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    接着定义一个测试的基础骨架类,后续针对每一类测试场景,在类中添加方法即可

    import org.junit.jupiter.api.Test;
    import org.mockito.Mockito;
    import static org.assertj.core.api.Assertions.assertThat;
    import static org.assertj.core.api.Assertions.assertThatThrownBy;
    import static org.mockito.ArgumentMatchers.anyString;
    import static org.mockito.Mockito.*;
    public class MyListMock  {
      // 各种测试方法
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    Mock add方法

    正常情况下,调用list 接口的add方法往列表中添加元素,返回true表示添加成功,否则反之。现在测试阶段可以通过mock方式控制其返回值(当然这并没有任何实际意义,仅仅只是为了属性相关的API)

    @Test
        void test(){
            //对MyList对象进行mock
            MyList listMock = Mockito.mock(MyList.class);
            //对象add 任何数据时 返回false
            when(listMock.add(anyString())).thenReturn(false);
            boolean added = listMock.add("hello");
            //通过断言判断返回值
            assertThat(added).isFalse();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    以上的方法非常的简单易懂,核心代码也很好理解,不做过多解释。此外还有另外一种API能够实现相同的功能,从语法的角度来讲,区别仅仅只是将目的状语前置

    @Test
        void test2(){
            //对MyList对象进行mock
            MyList listMock = Mockito.mock(MyList.class);
            //返回false 当对象添加任意string元素时
            doReturn(false).when(listMock).add(anyString());
            boolean added = listMock.add("hello");
            assertThat(added).isFalse();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    Mock 异常处理

    当程序内部发生异常时,来看看mock是如何处理的。

    @Test
        void test3ThrowException(){
            MyList mock = Mockito.mock(MyList.class);
            //添加数据时 抛出异常
            when(mock.add(anyString())).thenThrow(IllegalStateException.class);
            assertThatThrownBy(() -> mock.add("hello"))
                    .isInstanceOf(IllegalStateException.class);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    以上的代码仅仅只是对异常的类型对了判断。如果还需要对异常报错信息进行判断比对的话,请看下面的代码

    @Test
        void test4ThrowException(){
            MyList mock = Mockito.mock(MyList.class);
            //抛出异常 并指定异常信息
            doThrow(new IllegalStateException("error message")).when(mock).add(anyString());
            assertThatThrownBy(() -> mock.add("hello"))
                    .isInstanceOf(IllegalStateException.class)
                    .hasMessageContaining("error message");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    Mock 真实调用

    在需要的时候,mockio框架提供相关API,让特定的方法做真实的调用(调用真实方法逻辑)

    @Test
        void testRealCall(){
            MyList mock = Mockito.mock(MyList.class);
            when(mock.size()).thenCallRealMethod();
            assertThat(mock).hasSize(2);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Mock 定制返回

    这里的放回跟整体方法的返回在概念上并不一致。当List存在多个元素时,Mock框架可以对特定的元素进行mock

    @Test
    void testCustomReturn(){
      MyList mock = Mockito.mock(MyList.class);
      mock.add("hello");
      mock.add("world");
      //修改下标为0的值
      doAnswer(t -> "hello world").when(mock).get(0);
      String element = mock.get(0);
      assertThat(element).isEqualTo("hello world");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    RestController Mock

    Mock 框架同样支持对 Restful 风格的controller层面的代码进行mock,为了更加直观的看到演示效果,先定义一个简单的controller,内部定义了http 不同请求类型的方法。

    基础代码

    • 基础VO类

      import lombok.*;
      
      @Data
      @Builder
      @ToString
      @AllArgsConstructor
      @NoArgsConstructor
      public class EmployeeVO {
          private Long id;
          private String name;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
    • RestController

      import org.springframework.http.HttpStatus;
      import org.springframework.http.ResponseEntity;
      import org.springframework.web.bind.annotation.*;
      import java.util.Arrays;
      import java.util.List;
      import java.util.Map;
      
      @RestController
      public class MvcController {
      
          @GetMapping(value = "/employees")
          public Map<String,List<EmployeeVO>> getAllEmployees(){
              return Map.of("data",Arrays.asList(new EmployeeVO(100L,"kobe")));
          }
          @GetMapping(value = "/employees/{id}")
          public EmployeeVO getEmployeeById (@PathVariable("id") long id){
              return EmployeeVO.builder().id(id).name("kobe:" + id).build();
          }
          @DeleteMapping(value = "/employees/{id}")
          public ResponseEntity<HttpStatus> removeEmployee (@PathVariable("id") int id) {
              return new ResponseEntity<HttpStatus>(HttpStatus.ACCEPTED);
          }
          @PostMapping(value = "/employees")
          public ResponseEntity<EmployeeVO> addEmployee (@RequestBody EmployeeVO employee){
              return new ResponseEntity<EmployeeVO>(employee, HttpStatus.CREATED);
          }
          @PutMapping(value = "/employees/{id}")
          public ResponseEntity<EmployeeVO> updateEmployee (@PathVariable("id") int id,@RequestBody EmployeeVO employee){
              return new ResponseEntity<EmployeeVO>(employee,HttpStatus.OK);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31

      controller层定义了不同请求类型HTTP请求。接下来根据不同的请求类型分别进行mock。

      为了读者能够更加直观的进行阅读,首先定义Mock测试骨架类,后续不同场景测试代码在该骨架类中添加方法即可

      import com.fasterxml.jackson.databind.ObjectMapper;
      import org.junit.jupiter.api.Test;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
      import org.springframework.http.MediaType;
      import org.springframework.test.web.servlet.MockMvc;
      import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
      import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
      import static org.hamcrest.Matchers.hasSize;
      import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
      import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
      import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
      @WebMvcTest(MvcController.class)
      class MvcControllerTest {
       		@Autowired
          private MockMvc mvc;
        
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18

    Mock HTTP GET

    @Test
        void getAllEmployees() throws Exception{
            mvc.perform(MockMvcRequestBuilders
                            .get("/employees")
                    //接收header类型
                            .accept(MediaType.APPLICATION_JSON))
                    //打印返回
                    .andDo(print())
                    // 判断状态
                    .andExpect(status().isOk())
                    //取数组第一个值 进行比较
                    .andExpect(jsonPath("$.data[0].name").value("kobe"))
                    //取数组第一个值 进行比较
                    .andExpect(jsonPath("$.data[0].id").value(100L))
                    //判断返回长度
                    .andExpect(jsonPath("$.data", hasSize(1)));
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    Mock HTTP POST

    @Test
        void addEmployee() throws Exception {
            mvc.perform( MockMvcRequestBuilders
                    .post("/employees") // 指定post类型
                    .content(new ObjectMapper().writeValueAsString(new EmployeeVO(101L,"东方不败")))
                    .contentType(MediaType.APPLICATION_JSON)
                    .accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isCreated())
            //判断返回是否存在id字段
            .andExpect(MockMvcResultMatchers.jsonPath("$.id").exists());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    Mock HTTP PUT

    @Test
        void updateEmployee() throws Exception {
            mvc.perform( MockMvcRequestBuilders
                    //指定http 请求类型
                    .put("/employees/{id}", 2)
                    .content(new ObjectMapper().writeValueAsString(new EmployeeVO(2L,"东方不败")))
                    //请求header 类型
                    .contentType(MediaType.APPLICATION_JSON)
                    .accept(MediaType.APPLICATION_JSON))
                    .andDo(print())
                    .andExpect(status().isOk())
                    .andExpect(MockMvcResultMatchers.jsonPath("$.id").value(2L))
                    .andExpect(MockMvcResultMatchers.jsonPath("$.name").value("东方不败"));
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    Mock HTTP DELETE

    @Test
        void removeEmployee() throws Exception {
            mvc.perform( MockMvcRequestBuilders.delete("/employees/{id}", 1) )
                    .andExpect(status().isAccepted());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
  • 相关阅读:
    IDM的实用功能介绍+下载地址
    php-java-net-python-报修修改计算机毕业设计程序
    哈夫曼编码与二叉字典树
    SD-WAN不断冲击传统WAN架构
    GitLab-访问返回403 forbidden问题处理
    【Redis】Redis-cli命令行工具解析与应用(Redis专栏启动)
    Worthington用于细胞收获的胰蛋白酶&细胞释放程序
    SQL中通过QUALIFY语法过滤窗口函数简化代码
    3ds max 2024 V-Ray 6 ACES workflow 工作流设置
    windows安装jenkins2.346.1
  • 原文地址:https://blog.csdn.net/u013433591/article/details/128166873