如何为返回服务调用的Spring控制器编写JUnit测试并覆盖代码
Spring Boot控制器测试与代码覆盖率提升
问题背景
作为Spring和JUnit测试新手,希望通过测试控制器方法提升代码行覆盖率。目标控制器类如下:
@RestController @RequestMapping("/") public class SummaryController { private final SummaryService summaryService; @Autowired public SummaryController(SummaryService summaryService) { this.summaryService = summaryService; } @GetMapping("/summary") @ResponseBody public List<SummaryObject> getDirectoryList() { return summaryService.getSummary(); } }
这是一个Spring Boot/React Web应用,点击“summary”页面会加载本地文件目录内容的表格,控制器方法直接返回summaryService.getSummary()的结果。
自行编写的测试代码如下,但测试通过后未覆盖控制器中return summaryService.getSummary();这一行:
@WebMvcTest (TestController.class) public class TestController { List<SummaryObject> summaryObject; @Mock private SummaryService summaryService; @Test void testControllerMatch() { this.summaryObject = new ArrayList<>(); this.summaryObject.add(new SummaryObjectModel("file name", "file size", "last modified")); when(SummaryService.getSummary()).thenReturn(summaryObjectModel); assertEquals(SummaryObjectModel, SummaryService.getSummary()); } }
核心问题分析
你当前的测试仅验证了Mock的SummaryService行为,完全没有调用控制器的getDirectoryList方法,自然不会覆盖控制器中返回Service结果的代码行。要覆盖目标代码,必须模拟HTTP请求触发控制器方法的执行。
解决方案
1. 编写正确的控制器测试(覆盖目标代码行)
使用MockMvc模拟HTTP请求,调用控制器的/summary接口,验证控制器正确返回Service的数据。修正后的测试代码如下:
@WebMvcTest(SummaryController.class) public class SummaryControllerTest { @Autowired private MockMvc mockMvc; @MockBean private SummaryService summaryService; @Test void getDirectoryList_ShouldReturnSummaryList() throws Exception { // 构造测试用的SummaryObject列表 List<SummaryObject> mockSummaryList = new ArrayList<>(); mockSummaryList.add(new SummaryObject("file1.txt", "10KB", "2024-05-20")); // 配置Mock Service的返回值 when(summaryService.getSummary()).thenReturn(mockSummaryList); // 模拟GET请求并验证响应 mockMvc.perform(get("/summary") .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) // 验证响应状态码为200 .andExpect(jsonPath("$", hasSize(1))) // 验证返回列表长度为1 .andExpect(jsonPath("$[0].fileName").value("file1.txt")) // 验证第一个对象的fileName属性 .andExpect(jsonPath("$[0].fileSize").value("10KB")) // 验证fileSize属性 .andExpect(jsonPath("$[0].lastModified").value("2024-05-20")); // 验证lastModified属性 } }
此测试会触发控制器的getDirectoryList方法执行,从而覆盖return summaryService.getSummary();这一行代码。
2. 验证方法确实返回SummaryObject列表
除了通过jsonPath验证字段,还可以将响应JSON转换为对象列表,直接断言类型和内容:
@Test void getDirectoryList_ShouldReturnValidSummaryObjectList() throws Exception { List<SummaryObject> mockSummaryList = new ArrayList<>(); mockSummaryList.add(new SummaryObject("file1.txt", "10KB", "2024-05-20")); when(summaryService.getSummary()).thenReturn(mockSummaryList); // 执行请求并获取响应结果 MvcResult result = mockMvc.perform(get("/summary") .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andReturn(); // 将JSON响应转换为对象列表 String jsonResponse = result.getResponse().getContentAsString(); ObjectMapper objectMapper = new ObjectMapper(); List<SummaryObject> responseList = objectMapper.readValue( jsonResponse, new TypeReference<List<SummaryObject>>() {} ); // 断言返回结果的类型和内容 assertThat(responseList).isInstanceOf(List.class); assertThat(responseList.get(0)).isInstanceOf(SummaryObject.class); assertThat(responseList).isEqualTo(mockSummaryList); }
3. 针对该方法可扩展的其他测试
- 空列表场景测试:验证当Service返回空列表时,控制器正确返回空数组
@Test void getDirectoryList_ShouldReturnEmptyListWhenServiceHasNoData() throws Exception { when(summaryService.getSummary()).thenReturn(Collections.emptyList()); mockMvc.perform(get("/summary") .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$", hasSize(0))); }
- Service异常场景测试:验证当Service抛出异常时,控制器的错误响应逻辑(需根据实际异常处理配置调整断言)
@Test void getDirectoryList_ShouldReturnErrorWhenServiceFails() throws Exception { when(summaryService.getSummary()).thenThrow(new RuntimeException("加载目录失败")); mockMvc.perform(get("/summary") .contentType(MediaType.APPLICATION_JSON)) .andExpect(status().isInternalServerError()); }
内容的提问来源于stack exchange,提问作者dw885554
相关产品推荐
相关产品推荐

