简体   繁体   中英

Spring Webflux how to Mock response as Mono.error for WebClient Junit

I am working on Spring WebFlux project,

I am calling third party API to create entity using WebClient.

I want to persist errors if WebClient is getting 4xx as reponse code.

Below is the code which is requesting third party API

API.java

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

class API {
  public Mono<Response> create(Request req) {
    return WebClient.builder()
        .baseUrl("http://localhost:8080")
        .defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
        .build()
        .post()
        .uri("/v1/student")
        .contentType(MediaType.APPLICATION_JSON)
        .accept(MediaType.APPLICATION_JSON)
        .bodyValue(req.getData())
        .retrieve()
        .onStatus(HttpStatus::is4xxClientError, response -> handleError(response))
        .bodyToMono(Response.class);
  }

  private Mono<? extends ClientException> handleError(ClientResponse response) {
    return response
        .bodyToMono(ErrorResponse.class)
        .map(errorResponse -> new ClientException(errorResponse));
  }
}

If server is returning 4xx response, then I am just converting it into a specif error response.

Calling method is like below

APIService.java

public class APIService {
  API api;
  private ResponseHandler responseHandler;
  ErrorProcessor errorProcessor;


  public APIService(API api, ResponseHandler responseHandler, ErrorProcessor errorProcessor) {
    this.api = api;
    this.responseHandler = responseHandler;
    this.errorProcessor = errorProcessor;

  }

  public void process(Request request) {
    api.create(request)
        .doOnSuccess(response -> responseHandler.process(response))
        .doOnError(
            throwable -> {
              ClientException ce = (ClientException) throwable;
              errorProcessor.process(ce);
            })
        .block();
  }
}

Request.java

public class Request {
    final Map<String, Integer> data;

  public Request(int id) {
    data = new HashMap<>();
    data.put("counter", id);
  }

    public Map<String, Integer> getData() {
    return data;
  }
}

Response.java

public class Response {
  int id;
  public Response(int id) {
    this.id = id;
  }
}

ResponseHandler.java

import java.util.logging.Logger;

public class ResponseHandler {

  Logger log = Logger.getLogger(ResponseHandler.class.getName());

  public void process(Response response) {

    log.info("Id=" + response.id);
  }
}

Exception Handing class

ClientException.java

public class ClientException extends RuntimeException {

  private ErrorResponse errorResponse;

  public ClientException(ErrorResponse errorResponse) {
    this.errorResponse = errorResponse;
  }

  public ErrorResponse getErrorResponse() {
    return errorResponse;
  }
}

Error Processor

ErrorProcessor.java

import java.util.logging.Logger;

public class ErrorProcessor {
  Logger log = Logger.getLogger(ErrorProcessor.class.getName());

  public void process(ClientException ce) {

    log.info(ce.getErrorResponse().getErrorCode() + "=" + ce.getErrorResponse().getMessage());
  }
}

Error Response which is returned by API call

ErrorResponse.java

public class ErrorResponse {

  String errorCode;
  String message;

  public ErrorResponse(String errorCode, String message) {
    this.errorCode = errorCode;
    this.message = message;
  }

  public String getErrorCode() {
    return errorCode;
  }

  public String getMessage() {
    return message;
  }
}

While Writing Junit as below

APITest.java

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import reactor.core.publisher.Mono;

@ExtendWith(MockitoExtension.class)
public class APITest {
  @Mock API api;
  @Mock ErrorProcessor errorProcessor;
  @Mock ResponseHandler responseHandler;
  @InjectMocks APIService apiService;

  @Test
  public void givenRequest_shouldCreate() {
    // given
    Request request = new Request(1);
    Mockito.when(api.create(request)).thenReturn(Mono.just(new Response(1)));
    // when
    apiService.process(request);
    // then
    Mockito.verify(api, Mockito.times(1)).create(request);
    Mockito.verify(responseHandler,Mockito.times(1)).process(Mockito.any(Response.class));
  }
  //Failing Test
  @Test
  public void givenRequest_shouldThrow() {
    // given
    Request request = new Request(1);
    Mockito.when(api.create(request))
        .thenReturn(
            Mono.error(new ClientException(new ErrorResponse("0101", "This is failed request"))));
    // when
    apiService.process(request);
    // then

    Mockito.verify(errorProcessor, Mockito.times(1)).process(Mockito.any(ClientException.class));
  }
}

My test method givenRequest_shouldCreate is verifying that once API response is coming my ResponseHandler is getting called.

But Same way into test method givenRequest_shouldThrow , I want to verify that, if any error occured then my ErrorProcessor is getting called

When I ran above Test, I am getting following error

com.service.ClientException
    at com.service.APITest.givenRequest_shouldThrow(APITest.java:37)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:675)
    at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:125)
    at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:132)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:124)
    at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:74)
    at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:104)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:62)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:43)
    at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:35)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:202)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:198)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:69)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:135)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
    at java.util.ArrayList.forEach(ArrayList.java:1257)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
    at java.util.ArrayList.forEach(ArrayList.java:1257)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:125)
    at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:135)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:123)
    at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:122)
    at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:80)
    at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:229)
    at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$6(DefaultLauncher.java:197)
    at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:211)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:191)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:128)
    at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:69)
    at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
    at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
    at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
    Suppressed: java.lang.Exception: #block terminated with an error
        at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:93)
        at reactor.core.publisher.Mono.block(Mono.java:1663)
        at com.service.APIService.process(APIService.java:25)
        at com.service.APITest.givenRequest_shouldThrow(APITest.java:41)
        ... 63 more

So What I am trying to mock is, When I invoke create method then it will return Mono.error so inside .doOnError I want to persist the error. So I want to make sure that my errorHandler.saveError method is invoked or not.

Can some one please help me on this

Thanks

Alpesh

Your test should expect the process method to throw the exception, see example below. There are more fancy ways of asserting for exceptions listed in this thread .

//Failing Test
@Test
public void givenRequest_shouldThrow() {
    // given
    Request request = new Request(1);
    Mockito.when(api.create(request))
            .thenReturn(
                    Mono.error(new ClientException(new ErrorResponse("0101", "This is failed request"))));
    // when
    try {
        apiService.process(request);
        fail("Process should have thrown an exception");
    } catch (ClientException e) {
        // then
        assertEquals("0101", e.getErrorResponse().errorCode);
        assertEquals("This is failed request", e.getErrorResponse().message);
        assertNull(e.getMessage());
        Mockito.verify(errorProcessor, Mockito.times(1)).process(Mockito.any(ClientException.class));
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM