spring具体完善、强大的单元测试体系。下面,我们使用单元测试来测试班级的保存功能。 首先我们来到controller/KlassController.java,使用alt+enter快捷键来快速的生成测试文件: ![](https://img.kancloud.cn/7e/6e/7e6e40b701078ce284c99e87a9781b32_920x409.gif) 如我们所见,经过一系列的操作后IDEA为我们在对应的测试文件夹下生成了对应的测试文件`KlassControolerTest.java`。 ```java package com.mengyunzhi.springBootStudy.controller; import org.junit.Test; import static org.junit.Assert.*; public class KlassControllerTest { @Test ➊ public void save() { } } ``` * ➊ 表示save方法为一个测试用例 ![](https://img.kancloud.cn/c3/a4/c3a48574f3990ad6db6d390470221a0c_632x239.png) 点击此文件的任一绿色箭头,便可启动单元测试。此单元测试为基于`junit4`(一个在java下的测试工具)的测试,在此基础上如想进行Spring项目的单元测试,还需要进行以下配置。 ## Spring单元测试 将测试类使用`@RunWith(SpringRunner.class)`、`@SpringBootTest`来标明该类为一个spring测试类。 conroller/KlassControllerTest.java ``` package com.mengyunzhi.springBootStudy.controller; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; ① import org.springframework.test.context.junit4.SpringRunner; ① import static org.junit.Assert.*; @RunWith(SpringRunner.class) ② @SpringBootTest ② public class KlassControllerTest { @Test public void save() { } } ``` ## 测试SAVE方法 想测试控制器中的save方法,我们需要一个叫做`MockMvc 模拟MVC`的家伙来帮忙。 controller/KlassControllerTest.java ``` import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.test.web.servlet.MockMvc; ... @RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc ➊ public class KlassControllerTest { @Autowired ➋ MockMvc mockMvc; @Test public void save() { } } ``` * ➊ 在此类中自动配置MockMvc * ➋ 自动装配MockMvc对象 ### 模拟POST请求 代码如下: ``` import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultHandlers; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; ... @Test public void save() throws Exception { ➏ String url = "/Klass"; ➊ MockHttpServletRequestBuilder postRequest = MockMvcRequestBuilders.post(url) ➋ .contentType("application/json;charset=UTF-8") ➌ .content("{\"name\":\"测试单元测试班级\"}") ➍; this.mockMvc.perform(postRequest)➎ .andDo(MockMvcResultHandlers.print()➑) ➐ .andExpect(MockMvcResultMatchers.status().is(201));➒ } ``` * ➊ 请求地址,注意**不**需要`http://localhost:8080`。 * ➋ 构建一个指向url的模拟POST请求。 * ➌ 当前发送数据的格式为json;发送数据的编码为utf-8(可省略)。 * ➍ 发送的主体内容为:`{"name":"测试单元测试班级"}`,其中`\"`为`"`的转义符号,用于在字符串中输入`"`。 * ➎ 发送刚刚构建的postRequest请求。 * ➏ 该请求可能得不到正确的执行(比如URL地址写错了),未正常执行时将抛出异常。 * ➐ 成功发送请求后... * ➑ 打印请求的结果到控制台 * ➒ 然后进行断言 * ➓ 断言返回的状态码为201 > 阅读一些长的代码时,要尝试将其做为课文来读。至于这些功能是如何具体实现的,在面向对象的世界里,我们并不关心。 与augular的单元测试一样,spring的单元测试也可以独立进行,所以在执行单元测试前,我们完全停止项目的运行: ![](https://img.kancloud.cn/07/6b/076b40e5bdc87347800f037d25455111_647x98.png) 然后执行单元测试: ![](https://img.kancloud.cn/67/84/6784d510335e44a3fc105ff11c6a3d9b_748x156.png) 在测试方法前显示了绿色的对勾,表明该方式测试通过。 ### 测试中加入教师信息 在前面的测试中加入教师信息时,首先在数据库中了添加一条用于测试的教师信息: ![](https://img.kancloud.cn/ef/db/efdb234810f24d091a75bb34ba0fe6ee_678x123.png) 在此时由于后台整个项目未启动,所以此时数据库中并没有任何数据表(每次系统启动时创建新表、停止时删除创建的表),那么当然也就无法向数据表中添加用于测试的教师数据了。其实即使是我们想办法添加了测试的数据,单元测试在启动时也会自动去删除原有的数据表然后创建空的数据的表,所以只要我们的数据表策略是`create-drop`,那么就没有办法使用原来的方法添加测试教师数据。 每次单元测试时都清空数据表在单元测试中非常有必要的,这保证了我们每次启动单元测试时数据的初始环境都是一致的,减少了单元测试在不同环境下的不确定性。那么此时如果想添加测试教师,则需要在代码中进行添加: #### TeacherReopository 前面我们建立了操作Klass表的KlassRepository,参考该文件我们建立操作Teacher表的TeacherRepository repository/TeacherRepository.java ``` package com.mengyunzhi.springBootStudy.repository; import com.mengyunzhi.springBootStudy.entity.Teacher; import org.springframework.data.repository.CrudRepository; /** * 教师仓库 */ public interface TeacherRepository extends CrudRepository<Teacher, Long> { } ``` #### 自动装配 接着来到需要使用该仓库进行数据新增的controller/KlasscontrollerTest.java ```java import com.mengyunzhi.springBootStudy.repository.TeacherRepository; ... public class KlassControllerTest { @Autowired MockMvc mockMvc; /*教师仓库*/ @Autowired TeacherRepository teacherRepository; ① @Test public void save() throws Exception { ``` ### 请求主体中加入教师信息 最后,我们在单元测试中利用teacherRepository来新建一个测试教师,最获取其ID放置到POST请求的主体中。 controller/KlasscontrollerTest.java ``` /*教师仓库*/ @Autowired TeacherRepository teacherRepository; ① @Test public void save() throws Exception { Teacher teacher = new Teacher(); ➊ teacher.setName("潘杰"); ➋ teacher.setEmail("panjie@yunzhiclub.com"); ➋ teacher.setUsername("panjie"); ➋ teacherRepository.save(teacher); ➌ String content = String.format("{\"name\":\"测试单元测试班级\", \"teacher\": {\"id\":%s}}", teacher.getId()); ➍ String url = "/Klass"; MockHttpServletRequestBuilder postRequest = MockMvcRequestBuilders.post(url) .contentType("application/json;charset=UTF-8") .content(content); ② this.mockMvc.perform(postRequest) .andDo(MockMvcResultHandlers.print()) .andExpect(MockMvcResultMatchers.status().is(201)); } ``` * ➊ 实例化教师 * ➋ 设置教师的属性 * ➌ 将教师保存到数据库中 * ➍ 拼接请求主体对应的字符串 测试: ``` MockHttpServletRequest: HTTP Method = POST Request URI = /Klass Parameters = {} Headers = [Content-Type:"application/json;charset=UTF-8"] Body = {"name":"测试单元测试班级", "teacher": {"id":1}} Session Attrs = {} ``` ### 添加断言 我们前面的测试确认了以下内容: * [ ] 请求的地址是正确的 * [ ] 按接口发送数据,后台是能够成功接收的 * [ ] 后台处理后,返回201的状态码 但数据是否真的被保存成功了呢?下面,我们加入数据保存成功的断言。 ```java import org.junit.Assert; /*班级*/ @Autowired KlassRepository klassRepository; ① /** * 班级保存测试 * 1. 建立一个供测试的教师 * 2. 拼接请求的JSON串 * 3. 模拟请求并断言返回了201 * 4. 断言新增数据成功 * @throws Exception */ @Test public void save() throws Exception { Teacher teacher = new Teacher(); teacher.setName("潘杰"); teacher.setEmail("panjie@yunzhiclub.com"); teacher.setUsername("panjie"); teacherRepository.save(teacher); String content = String.format("{\"name\":\"测试单元测试班级\", \"teacher\": {\"id\":%s}}", teacher.getId()); String url = "/Klass"; MockHttpServletRequestBuilder postRequest = MockMvcRequestBuilders.post(url) .contentType("application/json;charset=UTF-8") .content(content); this.mockMvc.perform(postRequest) .andDo(MockMvcResultHandlers.print()) .andExpect(MockMvcResultMatchers.status().is(201)); List<Klass> klasses = (List<Klass>)➋ this.klassRepository.findAll(); ➊ Assert.assertEquals(klasses.size(), 1); ➌ Klass klass = klasses.get(0); ➍ Assert.assertEquals(klass.getName(), "测试单元测试班级"); ➎ Assert.assertEquals(klass.getTeacher().getName(), "潘杰"); ➏ } ``` * ➊ findAll()查询出数据表中的全部数据。 * ➋ 将返回的值转换为List<Klass>类型。 * ➌ 断言有1条班级数据 * ➍ 获取该班级数据 * ➎ 断言班级的名称为测试名称 * ➏ 断言班级的中的教师名称为测试名称 最行,执行单元测试,测试通过代码数据添加成功,方法正确。 # 参考文档 | 名称 | 链接 | 预计学习时长(分) | | --- | --- | --- | | 源码地址 | [https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step3.3.5](https://github.com/mengyunzhi/spring-boot-and-angular-guild/releases/tag/step3.3.5) | - | | Testing the Web Layer | [https://spring.io/guides/gs/testing-web/](https://spring.io/guides/gs/testing-web/) | 15 | | Spring MVC Test Framework | [https://docs.spring.io/spring/docs/5.2.1.RELEASE/spring-framework-reference/testing.html#spring-mvc-test-framework](https://docs.spring.io/spring/docs/5.2.1.RELEASE/spring-framework-reference/testing.html#spring-mvc-test-framework) | 30 |