You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring 6.1.0-M4渲染Page对象异常,升级后测试失败求助

Spring 6.1.0-M4升级后Page对象序列化测试失败问题解决

问题描述

将项目从Spring 6.0.10升级至Spring 6.1.0-M4后,控制器返回的Page<PostSummary>对象在测试中出现序列化失败,抛出HttpMessageNotWritableException,根源是UnsupportedOperationException。该功能在旧版本中运行正常,不确定是否需要额外配置。

控制器代码

@RequiredArgsConstructor
@RestController
@RequestMapping("/posts")
@Validated
public class PostController {
    private final PostRepository posts;

    @GetMapping(value = "", produces = APPLICATION_JSON_VALUE)
    public ResponseEntity<Page<PostSummary>> getAll(@RequestParam(defaultValue = "") String q,
                                                    @RequestParam(defaultValue = "") String status,
                                                    @RequestParam(defaultValue = "0") int page,
                                                    @RequestParam(defaultValue = "10") int size) {
        var postStatus = StringUtils.hasText(status) ? Status.valueOf(status) : null;
        var data = this.posts.findAll(Specifications.findByKeyword(q, postStatus), PageRequest.of(page, size))
                .map(p -> new PostSummary(p.getTitle(), p.getCreatedAt()));
        return ok(data);
    }
}

测试代码

@SpringJUnitWebConfig(classes = {Jackson2ObjectMapperConfig.class, WebConfig.class, TestDataConfig.class})
@ActiveProfiles("test")
public class PostControllerTestWithMockMvcWebTestClient {

    @Autowired
    PostController ctrl;

    WebTestClient rest;

    @Autowired
    PostRepository posts;

    @BeforeEach
    public void setup() {
        this.rest = MockMvcWebTestClient
                .bindToController(ctrl)
                .dispatcherServletCustomizer(dispatcherServlet -> dispatcherServlet.setEnableLoggingRequestDetails(true))
                .configureClient()
                .build();
    }

    @Test
    public void getAllPostsWillBeOk() throws Exception {
        when(this.posts.findAll(isA(Specification.class), isA(Pageable.class)))
                .thenReturn(new PageImpl<>(
                                List.of(
                                        Post.builder().title("test").content("content of test1").build(),
                                        Post.builder().title("test2").content("content of test2").build()
                                )
                        )
                );

        this.rest
                .get()
                .uri("/posts")
                .exchange()
                .expectStatus().isOk();
        // .expectBody().jsonPath("$.totalElements").isEqualTo(2);

        verify(this.posts, times(1)).findAll(isA(Specification.class), isA(Pageable.class));
        verifyNoMoreInteractions(this.posts);
    }
}

报错信息

2023-08-22 13:25:10,579 DEBUG [main] org.springframework.core.log.LogFormatUtils: Writing [Page 1 of 1 containing com.example.demo.web.PostSummary instances]
2023-08-22 13:25:10,638 WARN  [main] org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver: Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: (was java.lang.UnsupportedOperationException)]
2023-08-22 13:25:10,666 DEBUG [main] org.springframework.core.log.LogFormatUtils: [4b544732] HTTP GET /posts
2023-08-22 13:25:10,676 DEBUG [main] org.springframework.core.log.LogFormatUtils: [4b544732] [21abda60] Response 500 INTERNAL_SERVER_ERROR
2023-08-22 13:25:10,745 ERROR [main] org.springframework.test.web.reactive.server.ExchangeResult: Request details for assertion failure:

> GET /posts
> WebTestClient-Request-Id: [1]

No content

< 500 INTERNAL_SERVER_ERROR Internal Server Error
< Content-Type: [application/json]

{"content":[{"title":"test","createdAt":null},{"title":"test2","createdAt":null}],"pageable":{"sort":{"empty":true,"sorted":false,"unsorted":true}}}

======================  MockMvc (Server) ===============================

MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /posts
       Parameters = {}
          Headers = [WebTestClient-Request-Id:"1"]
             Body = <no character encoding set>
    Session Attrs = {}

Handler:
             Type = com.example.demo.web.PostController
           Method = com.example.demo.web.PostController#getAll(String, String, int, int)

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = org.springframework.http.converter.HttpMessageNotWritableException

ModelAndView:
        View name = null
             View = null
            Model = null

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 500
    Error message = null
          Headers = [Content-Type:"application/json"]
     Content type = application/json
             Body = {"content":[{"title":"test","createdAt":null},{"title":"test2","createdAt":null}],"pageable":{"sort":{"empty":true,"sorted":false,"unsorted":true}}}
    Forwarded URL = null
   Redirected URL = null
          Cookies = []



java.lang.AssertionError: Status expected:<200 OK> but was:<500 INTERNAL_SERVER_ERROR>
Expected :200 OK
Actual   :500 INTERNAL_SERVER_ERROR
<Click to see difference>


    at org.springframework.test.util.AssertionErrors.fail(AssertionErrors.java:59)
    at org.springframework.test.util.AssertionErrors.assertEquals(AssertionErrors.java:122)
    at org.springframework.test.web.reactive.server.StatusAssertions.lambda$assertStatusAndReturn$4(StatusAssertions.java:236)
    at org.springframework.test.web.reactive.server.ExchangeResult.assertWithDiagnostics(ExchangeResult.java:222)
    at org.springframework.test.web.reactive.server.StatusAssertions.assertStatusAndReturn(StatusAssertions.java:236)
    at org.springframework.test.web.reactive.server.StatusAssertions.isOk(StatusAssertions.java:68)
    at com.example.demo.web.PostControllerTestWithMockMvcWebTestClient.getAllPostsWillBeOk(PostControllerTestWithMockMvcWebTestClient.java:63)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
    at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)

问题原因

测试中创建PageImpl时只传入了内容列表,未指定分页参数(Pageable)和总元素数。PageImpl的默认实现里,未初始化的分页属性(如totalElements、number、size等)对应的getter方法会抛出UnsupportedOperationException。Spring 6.1.0-M4调整了Jackson序列化Page对象的逻辑,会尝试序列化更多分页属性,而旧版本未触发这些属性的序列化,因此升级后出现异常。

解决方案

在测试中创建PageImpl时,传入完整的分页参数,包括Pageable和总元素数。修改测试代码中的返回值部分:

when(this.posts.findAll(isA(Specification.class), isA(Pageable.class)))
        .thenReturn(new PageImpl<>(
                        List.of(
                                Post.builder().title("test").content("content of test1").build(),
                                Post.builder().title("test2").content("content of test2").build()
                        ),
                        PageRequest.of(0, 10), // 与控制器默认的page和size一致
                        2 // 总元素数,对应列表中的元素数量
                )
        );

修改后,PageImpl的所有分页属性都有了有效实现,Jackson序列化时不会再抛出异常,测试即可通过。

内容的提问来源于stack exchange,提问作者Hantsy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 19:58:12