使用Spock测试Spring 3控制器GET请求时出现错误求助
看起来你遇到的情况有点棘手——同一控制器里POST测试都正常,偏偏GET请求的测试出问题。先把你提到的关键代码整理出来,方便分析:
控制器代码
@RequestMapping(value = "/{accountHolderId}/xyz", method = RequestMethod.GET) public @ResponseBody BaseResponse getHistoryByHolderId(HttpServletRequest request, @PathVariable String accountHolderId) { // 你的业务逻辑 }
Spock测试片段(补全常见写法)
def "GetHistoryByHolderId"() { given: def accountHolderId = "test-holder-id" // 其他测试准备工作 when: def result = mockMvc.perform(get("/${accountHolderId}/xyz")) .andExpect(status().isOk()) .andReturn() then: // 响应断言逻辑 def response = new ObjectMapper().readValue(result.response.contentAsString, BaseResponse) response.success == true }
下面是几个大概率能解决问题的排查方向,你可以逐一试试:
1. 路径变量的传递与绑定问题
Spring 3对@PathVariable的绑定有个小坑:如果编译时没有保留参数名(比如开启了编译优化),框架可能无法自动匹配路径占位符和方法参数。哪怕你参数名和占位符都是accountHolderId,也可能出问题。试试显式指定参数名:
public @ResponseBody BaseResponse getHistoryByHolderId(HttpServletRequest request, @PathVariable("accountHolderId") String accountHolderId)
另外,检查测试里的路径拼接是否正确——比如控制器类上有没有额外的@RequestMapping前缀(比如/api),如果有的话,测试请求路径要写成/api/${accountHolderId}/xyz,别漏掉前缀。
2. HttpServletRequest的模拟问题
你的控制器方法依赖HttpServletRequest,如果测试里没正确模拟这个对象,很容易报错。比如控制器里用到了request.getHeader()、request.getParameter()这类方法,就得在测试里把这些调用mock出来:
given: def mockRequest = Mock(HttpServletRequest) // 模拟你需要的请求方法 mockRequest.getHeader("Authorization") >> "Bearer test-token" when: def result = mockMvc.perform(get("/${accountHolderId}/xyz") .requestAttr("javax.servlet.http.HttpServletRequest", mockRequest)) // 把mock对象注入请求 .andExpect(status().isOk()) .andReturn()
如果是用Spring上下文加载测试,也要确保request对象能被正确注入到测试环境中。
3. 请求方法与路径的匹配校验
虽然你控制器指定了method=GET,但还是要确认测试里发送的确实是GET请求——别不小心写成了post()或者其他方法。另外,可以开启Spring MVC的日志调试,看看实际请求的路径和方法是否和控制器的映射匹配,日志里会打印出请求匹配的过程,很容易定位问题。
4. 响应序列化/反序列化问题
如果测试是在断言响应体,先打印出result.response.contentAsString看看实际返回的内容是什么。有可能是BaseResponse缺少无参构造器、或者某些字段的序列化规则有问题,导致Jackson(Spring 3默认用的序列化工具)无法正确解析响应。
5. Spock与Spring 3的版本兼容性
Spring 3是比较老的版本了,如果你用的是Spock 2.x及以上版本,可能会有兼容性问题。建议检查依赖版本,换成Spock 1.x系列(比如1.3-groovy-2.5),这个版本和Spring 3的适配性更好。
最后给你一个完整的示例测试代码,覆盖了上面提到的关键点:
import com.fasterxml.jackson.databind.ObjectMapper import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.setup.MockMvcBuilders import spock.lang.Specification class YourControllerSpec extends Specification { MockMvc mockMvc YourController controller = new YourController() ObjectMapper objectMapper = new ObjectMapper() def setup() { // 初始化MockMvc mockMvc = MockMvcBuilders.standaloneSetup(controller).build() } def "GetHistoryByHolderId should return successful response"() { given: def accountHolderId = "12345" def mockRequest = Mock(HttpServletRequest) // 模拟控制器需要的request方法 mockRequest.getHeader("X-Request-Id") >> "test-123" when: def result = mockMvc.perform(get("/${accountHolderId}/xyz") .requestAttr("javax.servlet.http.HttpServletRequest", mockRequest)) .andExpect(status().isOk()) .andReturn() then: def actualResponse = objectMapper.readValue(result.response.contentAsString, BaseResponse) actualResponse.success == true } }
内容的提问来源于stack exchange,提问作者Saishav

