如何使用WebTestClient对JSON接口响应进行软断言(Soft Assert)?
如何使用WebTestClient对JSON接口响应进行软断言(Soft Assert)?
你遇到的问题核心原因其实很简单:WebTestClient的响应体是一次性资源,每次调用expectBody()都会读取并消费掉响应体。你原来用expectAll的写法里,第一个lambda中的r.expectBody()已经把响应体读完了,第二个lambda再调用expectBody()时,响应体已经是空的,所以才会抛出json can not be null or empty的错误。
要实现真正的软断言(即所有断言都执行完毕后再统一报告错误),我们可以换一种思路:先把响应体完整提取出来,再在提取后的内容上做批量断言。下面给你两种常用的解决方案:
方案一:提取响应体字符串,用AssertJ软断言配合JsonPath检查
AssertJ是Spring Boot测试依赖中默认包含的断言库,它的软断言可以完美满足你的需求,所有断言都会执行,最后一次性抛出所有失败的断言信息。
import org.assertj.core.api.SoftAssertions; import com.jayway.jsonpath.JsonPath; @Test public void testAssertJson() { // 先获取完整的响应体字符串(注意:blockFirst()适用于单响应场景) String responseBody = webClient.get().uri("/some-endpoint") .exchange() .expectStatus().isOk() .returnResult(String.class) .getResponseBody() .blockFirst(); // 使用AssertJ软断言执行所有检查 try (SoftAssertions softly = new SoftAssertions()) { // 检查第一个子分类的code和libelle softly.assertThat(JsonPath.read(responseBody, "$.subCategories[0].code")) .isEqualTo("1"); softly.assertThat(JsonPath.read(responseBody, "$.subCategories[0].libelle")) .isEqualTo("foo"); // 可以继续添加更多断言,比如第二个子分类 softly.assertThat(JsonPath.read(responseBody, "$.subCategories[1].code")) .isEqualTo("2"); softly.assertThat(JsonPath.read(responseBody, "$.subCategories[1].libelle")) .isEqualTo("bar"); // 最后触发断言检查,会抛出所有失败的断言信息 softly.assertAll(); } }
方案二:将响应体转换为POJO对象,直接断言对象属性
如果你的接口响应有对应的Java实体类,这种方式会更直观,避免写JsonPath表达式:
首先定义对应响应结构的实体类:
import lombok.Data; import java.util.List; @Data class ApiResponse { private List<SubCategory> subCategories; } @Data class SubCategory { private String code; private String libelle; }
然后编写测试代码:
import org.assertj.core.api.SoftAssertions; @Test public void testAssertJson() { // 将响应体直接转换为POJO对象 ApiResponse response = webClient.get().uri("/some-endpoint") .exchange() .expectStatus().isOk() .expectBody(ApiResponse.class) .returnResult() .getResponseBody(); // 用软断言检查对象属性 try (SoftAssertions softly = new SoftAssertions()) { softly.assertThat(response.getSubCategories()).hasSize(2); SubCategory firstCategory = response.getSubCategories().get(0); softly.assertThat(firstCategory.getCode()).isEqualTo("1"); softly.assertThat(firstCategory.getLibelle()).isEqualTo("foo"); SubCategory secondCategory = response.getSubCategories().get(1); softly.assertThat(secondCategory.getCode()).isEqualTo("2"); softly.assertThat(secondCategory.getLibelle()).isEqualTo("bar"); softly.assertAll(); } }
为什么原来的写法不行?
再强调下:expectAll的设计是用来收集多个不共享资源的断言错误,比如同时断言响应状态码、响应头、响应体,但如果多个断言都要读取响应体,就会出现资源被提前消费的问题。你原来的每个lambda都调用r.expectBody(),相当于每次都要重新读取响应体,而响应体只能被读取一次,所以第二个断言必然失败。
备注:内容来源于stack exchange,提问作者jmaniquet
相关产品推荐
相关产品推荐

