Spring Boot Mock单元测试学习总结

单元测试的方法有很多种,比如使用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;

1@RunWith(SpringJUnit4ClassRunner.class) 2@SpringApplicationConfiguration(classes=MockServletContext.class) 3@WebAppConfiguration 4public class StudentControllerTest { 5 6 @Autowired 7 private WebApplicationContext wac; 8 9 private MockMvc mockMvc; 10 11 @Before 12 public void setup() { 13 this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); // 构造MockMvc 14 } 15 // ... 16}

注意:

(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依赖)

1@RunWith(SpringJUnit4ClassRunner.class) 2@ContextConfiguration({"classpath*:/spring-context.xml"}) 3public class DemoTest { 4 5 @Mock 6 private StudentService studentService; 7 8 @InjectMocks 9 private StudentController studentController; 10 11 private MockMvc mockMvc; 12 13 @Before 14 public void setup() { 15 MockitoAnnotations.initMocks(this); 16 this.mockMvc = MockMvcBuilders.standaloneSetup(bookController).build(); 17 } 18 19 @Test 20 public void testXX() throws Exception { 21 //... 22 } 23}

主要是两个步骤:
(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实体类:

1public class Student implements Serializable{ 2 3 /** 4 * 5 */ 6 private static final long serialVersionUID = -9143765513634702342L; 7 8 private Long id; 9 10 private String name; 11 12 private String age; 13 14 private String address; 15 16 public String getName() { 17 return name; 18 } 19//省略get/set方法... 20}

View Code

定义IStudentService接口类:

1public interface IStudentService { 2 3 public List<Student> getStudentList(); 4 5 public int addStudent(Student student); 6 7 public Student getStudent(Long id); 8 9 public int updateStudent(Student student); 10 11 public int deleteStudent(Long id); 12}

View Code

定义IStudentService接口类实现:

1@Service 2public class StudentService implements IStudentService { 3 4 /** 5 * 定义一个线程安全的集合(集合验证时获取不到存的值,不影响学习mock测试方法) 6 */ 7 static Map<Long, Student> students = Collections.synchronizedMap(new HashMap<Long, Student>()); 8 9 @Override 10 public List<Student> getStudentList() { 11 List<Student> stuList = new ArrayList<Student>(); 12 stuList.addAll(students.values()); 13 return stuList; 14 } 15 16 @Override 17 public int addStudent(Student student) { 18 students.put(student.getId(), student); 19 return 0; 20 } 21 22 @Override 23 public Student getStudent(Long id) { 24 Student stu = students.get(id); 25 return stu; 26 } 27 28 @Override 29 public int updateStudent(Student student) { 30 Long id = student.getId(); 31 Student stu = students.get(id); 32 stu.setAddress(student.getAddress()); 33 stu.setAge(student.getAge()); 34 stu.setName(student.getName()); 35 students.put(id, stu); 36 return 0; 37 } 38 39 @Override 40 public int deleteStudent(Long id) { 41 students.remove(id); 42 return 0; 43 } 44}

View Code

定义Controller类:

1@RestController 2@RequestMapping(value="/stu") 3public class StudentController { 4 5 @Autowired 6 private IStudentService iStudentService; 7 8 @RequestMapping(value="/",method=RequestMethod.GET) 9 @ResponseBody 10 public List<Student> getStudentList(){ 11 List<Student> list = iStudentService.getStudentList(); 12 System.out.println("==="+list.size()); 13 return list; 14 } 15 16 @RequestMapping(value="/{id}",method=RequestMethod.GET) 17 @ResponseBody 18 public Student getStudent(@PathVariable(value="id")Long id){ 19 return iStudentService.getStudent(id); 20 } 21 22 @RequestMapping(value = "/", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) 23 @ResponseBody 24 public String addStudent(@ModelAttribute Student student) { 25 iStudentService.addStudent(student); 26 return "success"; 27 } 28 29 @RequestMapping(value = "/", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_UTF8_VALUE) 30 @ResponseBody 31 public String updateStudent(@ModelAttribute Student student) { 32 iStudentService.addStudent(student); 33 return "success"; 34 } 35 36 @RequestMapping(value="/{id}",method=RequestMethod.DELETE) 37 public String deleteStudent(@PathVariable Long id){ 38 iStudentService.deleteStudent(id); 39 return "success"; 40 } 41}

View Code

定义Controller测试类:

1@RunWith(SpringJUnit4ClassRunner.class) 2@SpringApplicationConfiguration(classes=MockServletContext.class) 3@WebAppConfiguration 4public class StudentControllerTest { 5 6 private static final Log log = LogFactory.getLog(StudentApplicationTest.class); 7 8// @Autowired 9// private WebApplicationContext context; 10 11 @Mock 12 private IStudentService iStudentService; 13 14 //@Mock: 创建一个Mock. 15 //@InjectMocks: 创建一个实例,其余用@Mock(或@Spy)注解创建的mock将被注入到用该实例中。 16 @InjectMocks 17 private StudentController studentController; 18 19 private MockMvc mvc; 20 21 private final String json = "[{\"id\":1,\"name\":\"xiao\",\"age\":27,\"address\":\"湖北\"}]"; 22 23 @Before 24 public void setUp(){ 25 MockitoAnnotations.initMocks(this); 26 mvc = MockMvcBuilders.standaloneSetup(studentController).build();////设置要mock的Controller类 27 //mvc = MockMvcBuilders.webAppContextSetup(context).build();//建议使用这种 28 } 29 30 @Test 31 public void testStudentController() throws Exception { 32 log.info("开始测试================="); 33 log.info("测试新增学生==================="); 34 RequestBuilder request = post("/stu/").param("id", "1") 35 .param("name", "xiao") 36 .param("age", "27") 37 .param("address","湖北"); 38 mvc.perform(request).andExpect(content().string(equalTo("success"))).andDo(print()); 39 40 log.info("测试获取学生============"); 41 request = get("/stu/"); 42 mvc.perform(request) 43 .andExpect(status().isOk()) 44 .andExpect(content().string(equalTo(json))); 45 } 46}

View Code

定义Service测试类:

1@RunWith(SpringJUnit4ClassRunner.class) 2@SpringApplicationConfiguration(classes=Application.class) 3@WebAppConfiguration 4public class StudentServiceTest { 5 6 @Autowired 7 private IStudentService iStudentSerivce; 8 9 @Test 10 public void testAddStudent(){ 11 Student stu = new Student(); 12 stu.setId(1l); 13 stu.setAge("27"); 14 stu.setName("xiaoming"); 15 stu.setAddress("China"); 16 int result = iStudentSerivce.addStudent(stu); 17 Assert.assertEquals(0, result); 18 } 19 20 @Test 21 public void testgetStudent(){ 22 Student stu = iStudentSerivce.getStudent(1l); 23 Assert.assertEquals(stu.getName(), "xiaohua"); 24 } 25}

View Code

附单元测试相关知识:

Junit单元测试:

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

Assert断言方法介绍:

1   1、assertEquals 2 3  函数原型1:assertEquals([String message],expected,actual) 4 5参数说明: 6 7message是个可选的消息,假如提供,将会在发生错误时报告这个消息。 8 9  expected是期望值,通常都是用户指定的内容。 10 11actual是被测试的代码返回的实际值。 12 13例:assertEquals("equals","1","1"); 14 15  函数原型2:assertEquals([String message],expected,actual,tolerance) 16 17参数说明: 18 19message是个可选的消息,假如提供,将会在发生错误时报告这个消息。 20 21  expected是期望值,通常都是用户指定的内容。 22 23  actual是被测试的代码返回的实际值。 24 25  tolerance是误差参数,参加比较的两个浮点数在这个误差之内则会被认为是 26 27  相等的。 28 29  例:assertEquals ("yes",5.8,11.0/2.0,0.5); 30 31  2、assertTrue 32 33   函数原型:assertTrue ([String message],Boolean condition) 34 35   参数说明: 36 37message是个可选的消息,假如提供,将会在发生错误时报告这个消息。 38 39   condition是待验证的布尔型值。 40 41   该断言用来验证给定的布尔型值是否为真,假如结果为假,则验证失败。当然,更有验证为假的测试条件: 42 43   函数原型:assertFalse([String message],Boolean condition) 44 45   该断言用来验证给定的布尔型值是否为假,假如结果为真,则验证失败。 46 47 例: assertTrue("true",1==1); 48 49 assertFalse("false",2==1); 50 51  3、assertNull 52 53  函数原型:assertNull([String message],Object object) 54 55参数说明: 56 57message是个可选的消息,假如提供,将会在发生错误时报告这个消息。 58 59  object是待验证的对象。 60 61  该断言用来验证给定的对象是否为null,假如不为null,则验证失败。相应地,还存在能够验证非null的断言: 62 63  函数原型:assertNotNull([String message],Object object) 64 65该断言用来验证给定的对象是否为非null,假如为null,则验证失败。 66 67例:assertNull("null",null); 68 69 assertNotNull("not null",new String()); 70 71  4、assertSame 72 73  函数原型:assertSame ([String message], expected,actual) 74 75参数说明: 76 77message是个可选的消息,假如提供,将会在发生错误时报告这个消息。 78 79  expected是期望值。 80 81  actual是被测试的代码返回的实际值。 82 83  该断言用来验证expected参数和actual参数所引用的是否是同一个对象,假如不是,则验证失败。相应地,也存在验证不是同一个对象的断言: 84 85  函数原型:assertNotSame ([String message], expected,actual) 86 87该断言用来验证expected参数和actual参数所引用的是否是不同对象,假如所引用的对象相同,则验证失败。 88 89例:assertSame("same",2,4-2); 90 91 assertNotSame("not same",2,4-3); 92 93  5、Fail 94 95  函数原型:Fail([String message]) 96 97参数说明: 98 99message是个可选的消息,假如提供,将会在发生错误时报告这个消息。 100 101  该断言会使测试立即失败,通常用在测试不能达到的分支上(如异常)。 102 103注:新版的Junit中,assertEquals 方法已经被废弃,它建议我们使用assertArrayEquals,旨在让我们测试一个方法的时候多传几种参数进行多种可能性测试。
点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )