单元测试的方法有很多种,比如使用Postman、SoapUI等工具测试,当然,这里的测试,主要使用的是基于RESTful风格的SpringMVC的测试,我们可以测试完整的Spring MVC流程,即从URL请求到控制器处理,再到视图渲染都可以测试。下面我主要总结下Spring Boot基于Mock的方式对控制层Controller和服务层Serivce的单元测试。尽管这种的文章已经有很多,我的总结只是作为自己学习的一个承载,总结有误的地方欢迎小伙伴们指正,同时也希望能帮助跟我一样还在学习的小伙伴们。

在介绍Mock API测试方法之前先介绍下Spring MVC测试框架提供的两种方式:

独立安装测试集成Web环境测试(此种方式并不会集成真正的web环境,而是通过相应的Mock API进行模拟测试,无须启动服务器)。

MockMvcBuilder介绍:

是用来构造MockMvc的构造器,其主要有两个实现:StandaloneMockMvcBuilder和DefaultMockMvcBuilder,StandaloneMockMvcBuilder继承了DefaultMockMvcBuilder。直接使用静态工厂MockMvcBuilders创建即可:

1. 集成Web环境测试:

MockMvcBuilders.webAppContextSetup(WebApplicationContext context):指定WebApplicationContext,将会从该上下文获取相应的控制器并得到相应的MockMvc;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes=MockServletContext.class)
@WebAppConfiguration
public class StudentControllerTest { @Autowired
private WebApplicationContext wac; private MockMvc mockMvc; @Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); // 构造MockMvc
}
// ...
}

注意:

(1)@WebAppConfiguration:测试环境使用,用来表示测试环境使用的ApplicationContext将是WebApplicationContext类型的;value指定web应用的根;
(2)通过@Autowired WebApplicationContext wac:注入web环境的ApplicationContext容器;
(3)然后通过MockMvcBuilders.webAppContextSetup(wac).build()创建一个MockMvc进行测试;

2.独立测试方式

MockMvcBuilders.standaloneSetup(Object... controllers):通过参数指定一组控制器,这样就不需要从上下文获取了;

(SpringMVC测试需要添加:mockito-core和mockito-all依赖)

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath*:/spring-context.xml"})
public class DemoTest { @Mock
private StudentService studentService; @InjectMocks
private StudentController studentController; private MockMvc mockMvc; @Before
public void setup() {
MockitoAnnotations.initMocks(this);
this.mockMvc = MockMvcBuilders.standaloneSetup(bookController).build();
} @Test
public void testXX() throws Exception {
//...
}
}

主要是两个步骤:
(1)首先自己创建相应的控制器,注入相应的依赖
(2)通过MockMvcBuilders.standaloneSetup模拟一个Mvc测试环境,通过build得到一个MockMvc。

perform:执行一个RequestBuilder请求,会自动执行SpringMVC的流程并映射到相应的控制器执行处理;
andExpect:添加ResultMatcher验证规则,验证控制器执行完成后结果是否正确;
andDo:添加ResultHandler结果处理器,比如调试时打印结果到控制台;
andReturn:最后返回相应的MvcResult;然后进行自定义验证/进行下一步的异步处理;

MockMvcRequestBuilders主要API:

MockHttpServletRequestBuilder get(String urlTemplate, Object... urlVariables):根据uri模板和uri变量值得到一个GET请求方式的MockHttpServletRequestBuilder;如get("/user/{id}", 1L);

MockHttpServletRequestBuilder post(String urlTemplate, Object... urlVariables):同get类似,但是是POST方法;

MockHttpServletRequestBuilder put(String urlTemplate, Object... urlVariables):同get类似,但是是PUT方法;

MockHttpServletRequestBuilder delete(String urlTemplate, Object... urlVariables) :同get类似,但是是DELETE方法;

MockHttpServletRequestBuilder options(String urlTemplate, Object... urlVariables):同get类似,但是是OPTIONS方法;

Mock测试过程:

1、mockMvc.perform执行一个请求(MockMvcRequestBuilders.get("/user/1")构造一个请求).
2、设置参数(这一步其实可以设置很多参数,MockMvc提供了丰富的方法)
3、mockMvc调用perform,调用controller的业务处理逻辑
4、perform返回ResultActions,返回操作结果,通过ResultActions,提供了统一的验证方式。(

ResultActions.andExpect添加执行完成后的断言,

ResultActions.andDo添加一个结果处理器,表示要对结果做点什么事情,比如此处使用MockMvcResultHandlers.print()输出整个响应结果信息。

ResultActions.andReturn表示执行完成后返回相应的结果。

测试说明:

1. 依赖包导入:pom.xml中仅依赖spring-boot-starter-test,它把会把测试相关的依赖全部引入。

2. 在测试类上的注解,常用的注解有三个:

@RunWith(SpringJUnit4ClassRunner.class)//引用Spring-Test测试框架支持
 @SpringApplicationConfiguration(classes = StartApp.class) // StartApp :可以是Spring Boot的启动类,也可以使用MockServletContext来构建一个空的WebApplicationContext,这样我们创建的StudentController就可以在@Before函数中创建并传递到MockMvcBuilders.standaloneSetup()函数中。
 @WebAppConfiguration //用来表示测试环境使用的ApplicationContext将是WebApplicationContext类型的

3. 测试类的文件结构,保持src/test/java和src/main/java结构一致,即:包+文件夹。测试配置文件任然用src/main/resources的。

根据如上介绍看下如下案例:

定义Student实体类:

public class Student implements Serializable{

    /**
*
*/
private static final long serialVersionUID = -9143765513634702342L; private Long id; private String name; private String age; private String address; public String getName() {
return name;
}
//省略get/set方法...
}

定义IStudentService接口类:

public interface IStudentService {

    public List<Student> getStudentList();

    public int addStudent(Student student);

    public Student getStudent(Long id);

    public int updateStudent(Student student);

    public int deleteStudent(Long id);
}

定义IStudentService接口类实现:

@Service
public class StudentService implements IStudentService { /**
* 定义一个线程安全的集合(集合验证时获取不到存的值,不影响学习mock测试方法)
*/
static Map<Long, Student> students = Collections.synchronizedMap(new HashMap<Long, Student>()); @Override
public List<Student> getStudentList() {
List<Student> stuList = new ArrayList<Student>();
stuList.addAll(students.values());
return stuList;
} @Override
public int addStudent(Student student) {
students.put(student.getId(), student);
return 0;
} @Override
public Student getStudent(Long id) {
Student stu = students.get(id);
return stu;
} @Override
public int updateStudent(Student student) {
Long id = student.getId();
Student stu = students.get(id);
stu.setAddress(student.getAddress());
stu.setAge(student.getAge());
stu.setName(student.getName());
students.put(id, stu);
return 0;
} @Override
public int deleteStudent(Long id) {
students.remove(id);
return 0;
}
}

定义Controller类:

@RestController
@RequestMapping(value="/stu")
public class StudentController { @Autowired
private IStudentService iStudentService; @RequestMapping(value="/",method=RequestMethod.GET)
@ResponseBody
public List<Student> getStudentList(){
List<Student> list = iStudentService.getStudentList();
System.out.println("==="+list.size());
return list;
} @RequestMapping(value="/{id}",method=RequestMethod.GET)
@ResponseBody
public Student getStudent(@PathVariable(value="id")Long id){
return iStudentService.getStudent(id);
} @RequestMapping(value = "/", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@ResponseBody
public String addStudent(@ModelAttribute Student student) {
iStudentService.addStudent(student);
return "success";
} @RequestMapping(value = "/", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@ResponseBody
public String updateStudent(@ModelAttribute Student student) {
iStudentService.addStudent(student);
return "success";
} @RequestMapping(value="/{id}",method=RequestMethod.DELETE)
public String deleteStudent(@PathVariable Long id){
iStudentService.deleteStudent(id);
return "success";
}
}

定义Controller测试类:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes=MockServletContext.class)
@WebAppConfiguration
public class StudentControllerTest { private static final Log log = LogFactory.getLog(StudentApplicationTest.class); // @Autowired
// private WebApplicationContext context; @Mock
private IStudentService iStudentService; //@Mock: 创建一个Mock.
//@InjectMocks: 创建一个实例,其余用@Mock(或@Spy)注解创建的mock将被注入到用该实例中。
@InjectMocks
private StudentController studentController; private MockMvc mvc; private final String json = "[{\"id\":1,\"name\":\"xiao\",\"age\":27,\"address\":\"湖北\"}]"; @Before
public void setUp(){
MockitoAnnotations.initMocks(this);
mvc = MockMvcBuilders.standaloneSetup(studentController).build();////设置要mock的Controller类
//mvc = MockMvcBuilders.webAppContextSetup(context).build();//建议使用这种
} @Test
public void testStudentController() throws Exception {
log.info("开始测试=================");
log.info("测试新增学生===================");
RequestBuilder request = post("/stu/").param("id", "1")
.param("name", "xiao")
.param("age", "27")
.param("address","湖北");
mvc.perform(request).andExpect(content().string(equalTo("success"))).andDo(print()); log.info("测试获取学生============");
request = get("/stu/");
mvc.perform(request)
.andExpect(status().isOk())
.andExpect(content().string(equalTo(json)));
}
}

定义Service测试类:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes=Application.class)
@WebAppConfiguration
public class StudentServiceTest { @Autowired
private IStudentService iStudentSerivce; @Test
public void testAddStudent(){
Student stu = new Student();
stu.setId(1l);
stu.setAge("27");
stu.setName("xiaoming");
stu.setAddress("China");
int result = iStudentSerivce.addStudent(stu);
Assert.assertEquals(0, result);
} @Test
public void testgetStudent(){
Student stu = iStudentSerivce.getStudent(1l);
Assert.assertEquals(stu.getName(), "xiaohua");
}
}

附单元测试相关知识:

Junit单元测试:

//所有测试方法执行前.执行一次,作用:整体初始化
@BeforeClass //所有测试方法完成后,执行一次,作用:销毁和释放资源
@AfterClass //每个测试方法前执行,作用:初始化方法
@Before //每个测试方法后执行,作用:还原现场
@After // 测试方法超过1000毫秒,记为超时,测试失败
@Test(timeout = 1000) // 测试方法期望得到的异常类,如果方法执行没有抛出指定的异常,则测试失败
@Test(expected = Exception.class) // 执行测试时将忽略掉此方法,如果用于修饰类,则忽略整个类
@Ignore(“not ready yet”)
@Test @RunWith
在JUnit中有很多个Runner,他们负责调用你的测试代码,每一个Runner都有各自的特殊功能,你要根据需要选择不同的Runner来运行你的测试代码。 如果我们只是简单的做普通Java测试,不涉及spring Web项目,你可以省略@RunWith注解,这样系统会自动使用默认Runner来运行你的代码。

Assert断言方法介绍:

   1、assertEquals

  函数原型1:assertEquals([String message],expected,actual)

参数说明:

message是个可选的消息,假如提供,将会在发生错误时报告这个消息。

  expected是期望值,通常都是用户指定的内容。

actual是被测试的代码返回的实际值。

例:assertEquals("equals","1","1");

  函数原型2:assertEquals([String message],expected,actual,tolerance)

参数说明:

message是个可选的消息,假如提供,将会在发生错误时报告这个消息。

  expected是期望值,通常都是用户指定的内容。

  actual是被测试的代码返回的实际值。

  tolerance是误差参数,参加比较的两个浮点数在这个误差之内则会被认为是

  相等的。

  例:assertEquals ("yes",5.8,11.0/2.0,0.5);

  2、assertTrue

   函数原型:assertTrue ([String message],Boolean condition)

   参数说明:

message是个可选的消息,假如提供,将会在发生错误时报告这个消息。

       condition是待验证的布尔型值。

   该断言用来验证给定的布尔型值是否为真,假如结果为假,则验证失败。当然,更有验证为假的测试条件:

          函数原型:assertFalse([String message],Boolean condition)

          该断言用来验证给定的布尔型值是否为假,假如结果为真,则验证失败。

       例: assertTrue("true",1==1);

              assertFalse("false",2==1);

  3、assertNull

  函数原型:assertNull([String message],Object object)

参数说明:

message是个可选的消息,假如提供,将会在发生错误时报告这个消息。

  object是待验证的对象。

  该断言用来验证给定的对象是否为null,假如不为null,则验证失败。相应地,还存在能够验证非null的断言:

  函数原型:assertNotNull([String message],Object object)

该断言用来验证给定的对象是否为非null,假如为null,则验证失败。

例:assertNull("null",null);

       assertNotNull("not null",new String());

  4、assertSame

  函数原型:assertSame ([String message], expected,actual)

参数说明:

message是个可选的消息,假如提供,将会在发生错误时报告这个消息。

  expected是期望值。

  actual是被测试的代码返回的实际值。

  该断言用来验证expected参数和actual参数所引用的是否是同一个对象,假如不是,则验证失败。相应地,也存在验证不是同一个对象的断言:

  函数原型:assertNotSame ([String message], expected,actual)

该断言用来验证expected参数和actual参数所引用的是否是不同对象,假如所引用的对象相同,则验证失败。

例:assertSame("same",2,4-2);

        assertNotSame("not same",2,4-3);

  5、Fail

  函数原型:Fail([String message])

参数说明:

message是个可选的消息,假如提供,将会在发生错误时报告这个消息。

  该断言会使测试立即失败,通常用在测试不能达到的分支上(如异常)。

:新版的Junit中,assertEquals 方法已经被废弃,它建议我们使用assertArrayEquals,旨在让我们测试一个方法的时候多传几种参数进行多种可能性测试。

Spring Boot Mock单元测试学习总结的更多相关文章

  1. Spring Boot干货系列:(十二)Spring Boot使用单元测试(转)

    前言这次来介绍下Spring Boot中对单元测试的整合使用,本篇会通过以下4点来介绍,基本满足日常需求 Service层单元测试 Controller层单元测试 新断言assertThat使用 单元 ...

  2. spring boot(10) 基础学习内容

    A Spring boot(10) 基础学习内容 B SpringBoot(16) 基础学习内容

  3. Spring Boot 2+gRPC 学习系列1:搭建Spring Boot 2+gRPC本地项目

    Spring Boot 2+gRPC 学习系列1:搭建Spring Boot 2+gRPC本地项目 https://blog.csdn.net/alinyua/article/details/8303 ...

  4. Spring Boot 的单元测试

    Spring Boot 的单元测试 引入依赖 testCompile group: 'org.springframework.boot', name: 'spring-boot-starter-tes ...

  5. 学习 Spring Boot:(二十九)Spring Boot Junit 单元测试

    前言 JUnit 是一个回归测试框架,被开发者用于实施对应用程序的单元测试,加快程序编制速度,同时提高编码的质量. JUnit 测试框架具有以下重要特性: 测试工具 测试套件 测试运行器 测试分类 了 ...

  6. Spring Boot 的单元测试和集成测试

    学习如何使用本教程中提供的工具,并在 Spring Boot 环境中编写单元测试和集成测试. 1. 概览 本文中,我们将了解如何编写单元测试并将其集成在 Spring Boot 环境中.你可在网上找到 ...

  7. Spring Boot使用单元测试

    一.Service层单元测试: 代码如下: package com.dudu.service;import com.dudu.domain.LearnResource;import org.junit ...

  8. Spring Boot 2 单元测试

    开发环境:IntelliJ IDEA 2019.2.2Spring Boot版本:2.1.8 IDEA新建一个Spring Boot项目后,pom.xml默认包含了Web应用和单元测试两个依赖包.如下 ...

  9. Spring Boot with Spring-Data-JPA学习案例

    0x01 什么是Spring Boot? Spring Boot是用来简化Spring应用初始搭建以及开发过程的全新框架,被认为是Spring MVC的"接班人",和微服务紧密联系 ...

随机推荐

  1. 【基于初学者的SSH】struts2 值栈的详解与struts2标签库+ognl表达式

    一:什么是值栈:struts2里面本身提供的一种存储机制,类似于域对象,值栈,可以存值和取值 特点:先进后出,最上面的元素叫做栈顶,也叫压栈. <s:debug></s:debug& ...

  2. zoj Continuous Login

    Continuous Login Time Limit: 2 Seconds      Memory Limit: 131072 KB      Special Judge Pierre is rec ...

  3. Laravel 支付宝异步通知 419报错

    支付宝在支付是有服务器通知和网页通知,一个在前端展示,一个在后台操作, laravel框架自带csrf_token验证. 所以我们需要把支付的路由跳过验证 可以在中间键的csrf配置中更改

  4. GitHub已将持续集成服务器Janky开源

    GitHub已将Janky开源,这是他们构建在Jenkins之上的持续集成服务器,并在其中增加了聊天自动化工具Hubot. 除了一般的Jenkins功能之外,Janky还通过Hubot对功能进行了补充 ...

  5. 【Python】Java程序员学习Python(六)— 流程控制、异常处理

    和Java语言一样,Python也有基本的流程控制,简单了解下即可. 一.流程控制的元素 条件 条件就是布尔值或者布尔值的表达式,要么是True要么是False. 代码块 在Python中,代码块不是 ...

  6. maven打包 springBoot 工程时,默认识别resources目录,习惯使用 resource 目录的需要手动指定静态资源目录

    最近项目开发,发现springBoot项目在使用maven打包时,我们静态资源文件都放在resource目录下面,大致如下: 在使用maven打包时,发现静态资源没有打进去.原来springBoot默 ...

  7. TFS工具(tf.exe)使用与强制解除锁定签出

    在工作区 NTP01_SUNTAI 中,NTP01TFSUser 为 签出 锁定了项 $/NTP01/EIPD/EIPD.Client/Views/Courseware/EditorOne.xaml. ...

  8. 关于Entity Framework更新的几种方式以及可能遇到的问题(附加类型“Model”的实体失败,因为相同类型的其他实体已具有相同的主键值)在使用 "Attach" 方法或者将实体的状态设置为 "Unchanged" 或 "Modified" 时如果图形中的任何实体具有冲突键值,则可能会发生上述行为

    在日常使用Entity Framework中,数据更新通常会用到.下面就简单封装了一个DBContext类 public partial class EFContext<T> : DbCo ...

  9. 通过ffplay实现摄像头preview

    通过ffplay实现摄像头preview 硬件平台:Jetson TK1 开发板(NVIDIA Tegra K1 Mobile Processor 32bit),宁波舜宇光电SP103A(OV1682 ...

  10. [控件] 心形加载的view

    心形加载的view 效果: 素材图片: 源码: StarView.h 与 StarView.m // // StarView.h // Star // // Created by XianMingYo ...