使用mockMVC对controller层进行接口调试

1,098 阅读5分钟

@TOC


背景

后端新增了一个对算法badCase排查功能,通过用户传入的内容按照节点成功或者失败走不同分支流程处理,流程结构如图所示。 在这里插入图片描述 判定流程的底层功能通过service层以RPC接口形式提供服务,结构定义如下:

 public ResponseDTO badCaseDetect(RequestDTO userInputParam);

输入参数为RequestDTO类型的userInputParam包括传入的数据集和指标信息以及用户query,输出FinalResult为各个步骤节点的判定结果。为了避免大量if-else,可以采用工厂策略或者状态模式优化结构,解除耦合,而且中途注入其他service出现了null的问题有对应记录,后续完善这部分优化内容。

service层功能完成以后,接下来就是构建controller层并进行调试。


一、controller层构建

RequestMapping我们自己来定义,可以支持get或者post方式请求,两者都是可以带请求体的。这里我们用post请求:

@Api(tags = "召回case排查接口")
@ResponseBody
@Controller
@RequestMapping("/recalling")
public class GroundingController{
    private static final Logger LOGGER = LoggerFactory.getLogger(GroundingController.class);
    @Resource
    GroundingService groundingService;
    @ApiOperation("recalling case 排查")
    @ResponseBody
    @RequestMapping(value = "/detect", method = RequestMethod.POST)
    public GroundingCaseDetectResponseDTO groundingCaseDetect(@ApiParam("query") @RequestParam(value = "query") String query,
                                                              @ApiParam("Id") @RequestParam(value = "id") String datasetId,
                                                              @ApiParam("dimIds") @RequestParam(value = "dimIds") List<String> dimIds) throws Exception {
        GroundingDTO groundingDTO = new GroundingDTO();
        groundingDTO.setQuery(query);
        groundingDTO.setId(Id);
      	groundingDTO.setDimIds(dimIds);
        LOGGER.error("请求内容:{}",groundingDTO);
        ResponseDTO finalResult = groundingService.badCaseDetect(groundingDTO);
        LOGGER.error("最终结果:{}",finalResult);
        return responseDTO;
    }
}


二、controller层测试

测试方法包括很多种,但是不同的方式对问题排查的便捷性不同。

1.本地集成测试方式-----本地先启动工程,然后直接用postman发请求,获取响应信息,工程启动以后可以根据请求响应情况进行调试。

2.本地单元测试方式-----通过Spring Test集成的mockMVC来进行测试,支持通过mock或者直接引入真实的service进行测试,完美集成Spring框架,便于本地断点调试发现定位问题。

3.合并发布后测试---,先合并到boe环境分支,构建发布到线下测试环境,然后通过postman构建请求发送,如果产生问题,可以通过全局logId访问日志平台,分析调用链路,排查问题,方便更直观更快速找到原因,但是合并代码冲突修正需要和其他同事沟通好。

代码层面先写个单测模块,过程中引入了mockMVC,为了求快直接用GPT生成的demo了,最初代码不一定是正确的

public class GroundingControllerTest extends BaseTest{

    private static final Logger LOGGER = LoggerFactory.getLogger(GroundingControllerTest.class);
    @InjectMocks  
    private GroundingController controller;
  
    @Mock  
    private GroundingService groundingService;
  
    private MockMvc mockMvc;  
  
    public GroundingControllerTest() {
        initMocks(this);  
        mockMvc = MockMvcBuilders.standaloneSetup(controller).build();  
    }  
  
    @Test  
    public void testGroundingCaseDetect() throws Exception {  
        String query = "*****************";
        String Id = "*********";
        List<String> dimIds = Arrays.asList("**********","*************");
        // 设置其他参数的值
        String cookie = "**********************";
        // 通过MockMvc发送请求,并传递查询参数
        MockHttpServletResponse response = mockMvc.perform(MockMvcRequestBuilders.post("/grounding/detect")
                        .contentType(MediaType.APPLICATION_JSON)
                        //.header("Cookie", cookie)
                        .param("query", query)
                        .param("Id", Id)
                       
                        .param("dimIds", String.join(",", dimIds))
                // 验证响应状态为200(或其他预期的HTTP状态码)
                .andExpect(status().isOk())
                // 返回响应结果进行进一步验证和处理
                .andReturn().getResponse();
        // 根据你的需求进行验证和断言
        LOGGER.error("测试结果:{}",response.getContentAsString());
    }  
}

1.先尝试本地postman测试

本地先启动工程,然后直接用postman发请求,获取响应信息,工程启动以后可以根据请求响应情况进行调试。最初测试结果如下:

在这里插入图片描述 报错203状态码,想到自己没有传用户cookie,于是从线下环境发布的平台地址F12刷新随便获取了一个用户cookie补充到请求里面。

然后继续请求,发现报错501状态码---“第三方服务请求失败”,这就有点看不懂了,于是转去进行mockMVC的单元测试。 在这里插入图片描述 中途报错也是可以在工程启动后查看单次请求响应情况的: wjDlxKw43u.jpg

2.使用mockMVC进行调试

之前的代码整体上结构看着问题不大,但是具体内容不一定正确,所以先运行一下这部分的test试下,发现最终结果是null,说明接口可能没有调用到。 在这里插入图片描述 于是我们可以以debug模式运行,发现问题出在 FinalResult finalResult = groundingService.badCaseDetect(groundingDTO); 运行到这一处时候,步入不了groundingService.badCaseDetect函数,直接跳过了,猜想可能是mock这个功能模拟了接口方法,并没有真实调用提供的接口方法(你猜它为什么叫mockMVC? 肯定是有mock功能的嘛),所以还是老老实实去官网查看对应的demo,把@Mock注解部分用真实的接口服务替换掉。

官网地址: chat.openai.com/c/150bc078-…

github上的demo: github.com/spring-proj… 在这里插入图片描述 并且可参考博客www.cnblogs.com/cxygg/p/174… (应该学会看官方的Api的demo,不是在别人的博客上面去抄)

对应修改之后的代码如下:

@AutoConfigureMockMvc
public class GroundingControllerTest extends BaseTest{

    private static final Logger LOGGER = LoggerFactory.getLogger(GroundingControllerTest.class);

    @Autowired
    private GroundingController controller;

    @Autowired
    WebApplicationContext context;

    //在before里面初始化
    private MockMvc mockMvc;

    @Before
    public void before() {
        //初始化mockMvc
        //可以测试单个controller
        //mockMvc = MockMvcBuilders.standaloneSetup(mockMvcController).build();

        //可以测试所有controller
        mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
    }
 //..............后面都不用动
    }  
}

修改完成以后继续运行,发现程序跑通了,输出了预期的结果。

3.使用mockMVC和本地不同

注意:使用mockMVC通过了不代表本地postman测试一样会成功,有时两者还是有区别的。修改完成以后继续调试,本地postman也通过了。

然后我们就可以愉快的合并代码发布到boe测试环境了,中途注意一点,合并代码时候,commit成功了,先本地测试一下,能正常跑通再push上去,不然提交了运行不了的代码,污染环境,就很烦。


总结

对于controller层的测试,后续可以用Spring Test3.2版本之后提供的mockMVC功能,它支持单模块测试,也支持端到端测试;支持mock,也支持直接调用原始service接口,出了问题也方便我们本地debug断点调试定位问题,和Junit一样无缝集成Spirng框架,所以有时侯用mockMVC会方便很多。但是注意使用mockMVC通过了不代表本地postman测试一样会成功,某些场景下两者还是有区别的。