Spring Boot Mock
创始人
2024-03-16 06:25:02
0

Spring MockMvc

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

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

Maven依赖

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

org.springframework.bootspring-boot-starter-testtest

org.mockitomockito-core4.8.1

Mockito 基本使用

基础代码

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

import java.util.AbstractList;
public class MyList extends AbstractList {@Overridepublic String get(int index) {//注意 get方法默认返回给 null,方便后续mockreturn null;}@Overridepublic int size() {return 1;}
}

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

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  {// 各种测试方法
}

Mock add方法

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

@Testvoid test(){//对MyList对象进行mockMyList listMock = Mockito.mock(MyList.class);//对象add 任何数据时 返回falsewhen(listMock.add(anyString())).thenReturn(false);boolean added = listMock.add("hello");//通过断言判断返回值assertThat(added).isFalse();}

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

@Testvoid test2(){//对MyList对象进行mockMyList listMock = Mockito.mock(MyList.class);//返回false 当对象添加任意string元素时doReturn(false).when(listMock).add(anyString());boolean added = listMock.add("hello");assertThat(added).isFalse();}

Mock 异常处理

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

@Testvoid test3ThrowException(){MyList mock = Mockito.mock(MyList.class);//添加数据时 抛出异常when(mock.add(anyString())).thenThrow(IllegalStateException.class);assertThatThrownBy(() -> mock.add("hello")).isInstanceOf(IllegalStateException.class);}

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

@Testvoid 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");}

Mock 真实调用

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

@Testvoid testRealCall(){MyList mock = Mockito.mock(MyList.class);when(mock.size()).thenCallRealMethod();assertThat(mock).hasSize(2);}

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");
}

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;
    }
    
  • 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> 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 removeEmployee (@PathVariable("id") int id) {return new ResponseEntity(HttpStatus.ACCEPTED);}@PostMapping(value = "/employees")public ResponseEntity addEmployee (@RequestBody EmployeeVO employee){return new ResponseEntity(employee, HttpStatus.CREATED);}@PutMapping(value = "/employees/{id}")public ResponseEntity updateEmployee (@PathVariable("id") int id,@RequestBody EmployeeVO employee){return new ResponseEntity(employee,HttpStatus.OK);}
    }
    

    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 {@Autowiredprivate MockMvc mvc;}
    

Mock HTTP GET

@Testvoid 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)));}

Mock HTTP POST

@Testvoid 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());}

Mock HTTP PUT

@Testvoid 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("东方不败"));}

Mock HTTP DELETE

@Testvoid removeEmployee() throws Exception {mvc.perform( MockMvcRequestBuilders.delete("/employees/{id}", 1) ).andExpect(status().isAccepted());}

相关内容

热门资讯

AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AWR报告解读 WORKLOAD REPOSITORY PDB report (PDB snapshots) AW...
AWS管理控制台菜单和权限 要在AWS管理控制台中创建菜单和权限,您可以使用AWS Identity and Access Ma...
​ToDesk 远程工具安装及... 目录 前言 ToDesk 优势 ToDesk 下载安装 ToDesk 功能展示 文件传输 设备链接 ...
群晖外网访问终极解决方法:IP... 写在前面的话 受够了群晖的quickconnet的小水管了,急需一个新的解决方法&#x...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
Azure构建流程(Power... 这可能是由于配置错误导致的问题。请检查构建流程任务中的“发布构建制品”步骤,确保正确配置了“Arti...