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

相关内容

热门资讯

前端-session、jwt 目录:   (1)session (2&#x...
linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
关于测试,我发现了哪些新大陆 关于测试 平常也只是听说过一些关于测试的术语,但并没有使用过测试工具。偶然看到编程老师...
前缀和与对数器与二分法 1. 前缀和 假设有一个数组,我们想大量频繁的去访问L到R这个区间的和,...
nodejs:本地安装nvm实... 一、背景-使用不同版本node的原因 vue3+ts、nuxt3版本,node...
JAVA集合知识整理 Java集合知识整理 HashMap相关 HashMap的底层数据结构:jdk1.8之...
无刷直流电机介绍及单片机控制实... 无刷直流电机介绍及单片机控制实例前言基本概念优势与劣势使用寿命基本结构使用单片机控制实例电子调速器&...
fwdiary(2) dp2 1.传纸条  AcWing 275. 传纸条 - AcWing 走两条路,走一条最大的...
常用的DOS命令 常用的DOS命令 DOS(Disk Operating System,磁...
<C++> 类和对象(下) 1.const成员函数将const修饰的“成员函数”称之为const成员函数,cons...