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

如何为WebClient的.onStatus(4xx错误)块编写测试用例以覆盖代码

如何覆盖WebClient 4xx错误处理块的单元测试

问题背景

我编写的RepoService类中,WebClient调用接口的.onStatus(HttpStatus::is4xxClientError)处理块内代码未被测试覆盖,现有Mockito单元测试仅覆盖正常返回场景,如何编写测试用例覆盖该块逻辑?

RepoService 代码

public class RepoService {
    @Autowired
    private WebClient webClient;
    
    public DIResponse createIdentity(DICreateRequest diCreateRequest) {
            CreateReq createReq = diCreateRequestMapper.setRequest(diCreateRequest);
            DIResponse diResponse = null;
    
            UriComponents uriBuilder = UriComponentsBuilder
                    .fromUriString(url)
                    .build(false);
    
            DIResponse apiResponse = webClient
                    .post()
                    .uri(uriBuilder.toUriString())
                    .body(Mono.just(createReq), CreateReq.class) 
                    .retrieve()
                    .onStatus(HttpStatus::is4xxClientError, clientResponse -> clientResponse.bodyToMono(DIBadRequest.class).flatMap(errorResponse -> {
                        LOGGER.error(CLIENT_ERROR, "POST", clientResponse.statusCode(), errorResponse);
                        return Mono.error(new DataAccessException(ERROR_STATUS +clientResponse.statusCode()+ ERROR_DESC+ errorResponse.getDetail()));
                    }))
                    .bodyToMono(DIResponse.class)
                    .block();
    
    return apiResponse;
     }
    }

现有Mockito单元测试代码

@Mock
private WebClient webClient;

@Mock
WebClient.RequestHeadersUriSpec requestHeadersUriSpecMock;
@Mock
WebClient.RequestBodyUriSpec requestBodyUriSpecMock;

@Mock
WebClient.RequestBodySpec requestBodySpecMock;

@Mock
WebClient.RequestHeadersSpec requestHeadersSpecMock;

@Mock
WebClient.ResponseSpec responseSpecMock;

@InjectMocks
private RepoService repositoryService;

@Test
void testCreateIdentity() throws IOException {
    

    DICreateRequest mockDICreateRequest = getMockDICreatReq();
    CreateReq createReq = createDI();
    DIResponse mockDIResponse = getDI();

when(diCreateRequestMapper.setRequest(mockDICreateRequest)).thenReturn(createReq);

when(webClient.post()).thenReturn(requestBodyUriSpecMock);
when(requestBodyUriSpecMock.uri((URI) any())).thenReturn(requestBodySpecMock);
when(requestBodySpecMock.body(any())).thenReturn(requestHeadersSpecMock);
when(requestHeadersSpecMock.retrieve()).thenReturn(responseSpecMock);
when(responseSpecMock.onStatus(any(), any())).thenReturn(responseSpecMock);
Mockito.when(responseSpecMock.bodyToMono(DIResponse.class)).thenReturn(Mono.just(mockDIResponse));

    DIResponse result = repositoryService.createIdentity(mockDICreateRequest);

    Assert.assertEquals(mockDIResponse.getId(), result.getId());
}

解决方案

要覆盖4xx错误处理逻辑,需要模拟WebClient返回4xx状态码并触发.onStatus中的错误处理逻辑,具体步骤如下:

1. 新增Mock对象

在测试类中添加ClientResponse的Mock,用来模拟4xx响应:

@Mock
private ClientResponse clientResponse;

2. 编写4xx场景测试用例

@Test
void testCreateIdentity_4xxError() {
    // 准备测试数据
    DICreateRequest mockDICreateRequest = getMockDICreatReq();
    CreateReq createReq = createDI();
    DIBadRequest mockErrorResponse = new DIBadRequest();
    mockErrorResponse.setDetail("参数格式错误");
    HttpStatus badRequestStatus = HttpStatus.BAD_REQUEST;

    // 映射请求转换逻辑
    when(diCreateRequestMapper.setRequest(mockDICreateRequest)).thenReturn(createReq);

    // 模拟WebClient调用链基础流程
    when(webClient.post()).thenReturn(requestBodyUriSpecMock);
    when(requestBodyUriSpecMock.uri((String) any())).thenReturn(requestBodySpecMock);
    when(requestBodySpecMock.body(eq(Mono.just(createReq)), eq(CreateReq.class))).thenReturn(requestHeadersSpecMock);
    when(requestHeadersSpecMock.retrieve()).thenReturn(responseSpecMock);

    // 关键:触发4xx错误处理逻辑
    when(responseSpecMock.onStatus(eq(HttpStatus::is4xxClientError), any())).thenAnswer(invocation -> {
        // 获取传入的错误处理函数
        Function<ClientResponse, Mono<? extends Throwable>> errorHandler = invocation.getArgument(1);
        // 模拟ClientResponse的状态码和错误体
        when(clientResponse.statusCode()).thenReturn(badRequestStatus);
        when(clientResponse.bodyToMono(DIBadRequest.class)).thenReturn(Mono.just(mockErrorResponse));
        // 执行错误处理函数,得到错误Mono
        Mono<? extends Throwable> errorMono = errorHandler.apply(clientResponse);
        // 让后续的bodyToMono返回错误Mono,触发block()时抛出异常
        when(responseSpecMock.bodyToMono(DIResponse.class)).thenReturn(errorMono.flatMap(Mono::error));
        return responseSpecMock;
    });

    // 执行测试并捕获异常
    DataAccessException exception = Assert.assertThrows(DataAccessException.class, () -> {
        repositoryService.createIdentity(mockDICreateRequest);
    });

    // 验证异常信息是否符合预期
    String expectedMessage = ERROR_STATUS + badRequestStatus + ERROR_DESC + mockErrorResponse.getDetail();
    Assert.assertEquals(expectedMessage, exception.getMessage());

    // 验证错误日志是否被正确记录
    verify(LOGGER).error(eq(CLIENT_ERROR), eq("POST"), eq(badRequestStatus), eq(mockErrorResponse));
}

核心说明

  • 使用thenAnswer捕获.onStatus中的错误处理函数,手动触发执行
  • 模拟ClientResponse返回4xx状态码和对应的错误响应体
  • 确保bodyToMono(DIResponse.class)返回错误Mono,让block()抛出预期的DataAccessException
  • 最后验证异常信息和日志输出,确保错误处理逻辑完全执行

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 13:27:51