由单元测试发现的代码 Bug

由单元测试发现的代码 Bug

背景概述

背景来自于给 Apache Hertzbeat 编写单元测试,发现的一个问题。这个问题也是历史遗留问题,之前的测试类是注释的状态。

15:18:38.013 [main] WARN org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver -- Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: (was java.lang.UnsupportedOperationException)]

java.lang.AssertionError: Status expected:<200> but was:<500>
Expected :200
Actual   :500

没有任何其他信息,报错如上。

单元测试和代码如下:

@GetMapping
@Operation(summary = "Example Query the alarm definition list",
           description = "You can obtain the list of alarm definitions by querying filter items")
public ResponseEntity<Message<Page<AlertDefine>>> getAlertDefines(
    @Parameter(description = "Alarm Definition ID", example = "6565463543") @RequestParam(required = false) List<Long> ids,
    @Parameter(description = "Search-Target Expr Template", example = "x") @RequestParam(required = false) String search,
    @Parameter(description = "Alarm Definition Severity", example = "6565463543") @RequestParam(required = false) Byte priority,
    @Parameter(description = "Sort field, default id", example = "id") @RequestParam(defaultValue = "id") String sort,
    @Parameter(description = "Sort mode: asc: ascending, desc: descending", example = "desc") @RequestParam(defaultValue = "desc") String order,
    @Parameter(description = "List current page", example = "0") @RequestParam(defaultValue = "0") int pageIndex,
    @Parameter(description = "Number of list pages", example = "8") @RequestParam(defaultValue = "8") int pageSize) {

    Page<AlertDefine> alertDefinePage = alertDefineService.getAlertDefines(ids, search, priority, sort, order, pageIndex, pageSize);
    return ResponseEntity.ok(Message.success(alertDefinePage));
}

测试类:

@Test
void getAlertDefines() throws Exception {

    when(alertDefineService.getAlertDefines(List.of(1L), "Test", (byte) 1, "id", "desc", 1, 10))
        .thenReturn(new PageImpl<>(Collections.singletonList(alertDefine)));

    mockMvc.perform(MockMvcRequestBuilders.get(
        "/api/alert/defines")
                    .param("ids", "1")
                    .param("search", "Test")
                    .param("priority", "1")
                    .param("sort", "id")
                    .param("order", "desc")
                    .param("pageIndex", "1")
                    .param("pageSize", "10")
                    .accept(MediaType.APPLICATION_JSON))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$.code").value((int) CommonConstants.SUCCESS_CODE))
        .andExpect(jsonPath("$.data.content[0].app").value("linux"))
        .andExpect(jsonPath("$.data.content[0].id").value("9"))
        .andExpect(jsonPath("$.data.content[0].metric").value("disk"))
        .andReturn();
}

分析

经过 google 确定了可能发生问题的原因:

  1. 使用了 Arrays.asList(T t) 方法
  2. 实体类种属性为 null 值,而没有使用 @JsonIgnore 等注解
  3. 没有使用包装类型而使用了 int,long 等基元类型
  4. 在 spring 反向解析对象时,会调用 is 开头返回 boolean 类型的方法

总共 google 到了以上四种原因,经过和项目中的其他实体类对比和分析,进行逐一排除。最后发现都没有符合上述问题的选项。

继而看到了返回值的对象类型,发现不是单独的实体类型,而是一个 Page 类型的对象,想到,会不会是 Page 中某个属性为空造成的 Json 序列化异常。

在 Controller 中加入以下代码片段:

Page<AlertDefine> alertDefinePage = alertDefineService.getAlertDefines(ids, search, priority, sort, order, pageIndex, pageSize);

// 模拟序列化过程进行调试
String json = JsonUtil.toJson(alertDefinePage);
System.out.println(json);

return ResponseEntity.ok(Message.success(alertDefinePage));
}

运行测试,发现确实报错:

15:32:00.342 [main] ERROR org.apache.hertzbeat.common.util.JsonUtil -- (was java.lang.UnsupportedOperationException) (through reference chain: org.springframework.data.domain.PageImpl["pageable"]->org.springframework.data.domain.Unpaged["offset"])
com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.UnsupportedOperationException) (through reference chain: org.springframework.data.domain.PageImpl["pageable"]->org.springframework.data.domain.Unpaged["offset"])

这样,我们就从控制台看到了更多的错误信息,Unpaged 中的 offset 貌似有点问题。输出它看看

System.out.println(alertDefinePage.getPageable().getOffset());

jakarta.servlet.ServletException: Request processing failed: java.lang.UnsupportedOperationException

果不其然,确实有问题。再次 google 之后,找到了社区 issue:https://github.com/spring-projects/spring-data-commons/issues/2987,其中一位应该是社区维护者,更是说出 Pageable 从未打算被序列化。🥲

在这里插入图片描述

修复

确定了问题之后,寻找修复的办法。

根据社区 issue 进展,总共有两种修复办法:

  1. 通过 PageImpl 重新包装 Page 对象:

    Pageable pageable = PageRequest.of(0, 10);
    PageImpl page = new PageImpl<>(myArraylist, pageable, myArraylist.size());
    
  2. SpringDataJacksonConfiguration.PageModule 添加到 Jackson2ObjectMapperBuilder

    @Bean
    public Jackson2ObjectMapperBuilder objectMapperBuilder(SpringDataJacksonConfiguration.PageModule pageModule) {
           Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
           builder.modules(pageModule);
          return builder;
    }
    
  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值