Search code examples
javamockitowebclient

Mocking org.springframework.web.reactive.function.client.WebClient.ResponseSpec#onStatus input parameters


Considering the following code:

    public Mono<Void> doStuff() {

        return this.requestStuff()
            .onStatus(HttpStatus::is5xxServerError,
                    clientResponse -> {
                    aMethodIWouldLikeToTest(clientResponse);
                    return Mono.error(new MyCustomException("First error I would like to test"));
                    })
            .onStatus(HttpStatus::is4xxClientError,
                    clientResponse -> {
                    aMethodIWouldLikeToTest(clientResponse);
                    return Mono.error(new MyCustomException("Second error I would like to test"));
                    })
            .bodyToMono(String.class)
            .flatMap(x -> anotherMethodIManagedToTest(x)))

    }

My first goal was to test anotherMethodIManagedToTest(x) that was achieved using:

    import org.springframework.web.reactive.function.client.WebClient;

    ...

    @Mock
    private WebClient.ResponseSpec responseSpec;

    private String desiredInputParam = "There is another black spot on the sun today!";

    ...

    @Test
    public void allGood_anotherMethodIManagedToTest_success {

        ...

        ClassUnderTest classUnderTest = new classUnderTest()
        ClassUnderTest classUnderTestSpy = spy(classUnderTestSpy);
        doReturn(responseSpec).when(classUnderTestSpy).requestStuff();

        when(responseSpec.onStatus(any(), any())).thenReturn(responseSpec);
        when(responseSpec.bodyToMono(String.class)).thenReturn(Mono.just(desiredInputParam));

        Mono<Void> result = classUnderTestSpy.doStuff();

        // Bunch of assertions for anotherMethodIManagedToTest(String desiredInputParam) performed with success ...

    }

Now I would like to create additional tests to test the event of a 5xxServerError and the event of a 4xxClientError but I am having a hard time figuring how to:

  • Mock the response of HttpStatus::is5xxServerError
  • Mock the response of HttpStatus::is4xxServerError
  • Mock the clientResponse in order to test aMethodIWouldLikeToTest(org.springframework.web.reactive.function.client.ClientResponse clientResponse)

Any suggestions on how to perform these actions?

Please note that I cannot really use any PowerMock alternatives (still interested if this is the only way to acheive my goal) all answers with standard Mockito are preferred.


Solution

  • I am sorry about my late response. It is possible to use mockito for your test case:

    • you need to get the HttpStatus to check it in the onStatus method
    • so first create an abstract class (to avoid implementing all the methods) that implements WebClient.ResponseSpec
    • you can create a mock from this class instead of ResponseSpec
    • I added 2 methods in this class:
    abstract class CustomMinimalForTestResponseSpec implements WebClient.ResponseSpec {
    
            public abstract HttpStatus getStatus();
    
            public WebClient.ResponseSpec onStatus(Predicate<HttpStatus> statusPredicate, Function<ClientResponse, Mono<? extends Throwable>> exceptionFunction) {
                if (statusPredicate.test(this.getStatus())) exceptionFunction.apply(ClientResponse.create(HttpStatus.OK).build()).block();
                return this;
            }
          }
    
    • the getStatus will be used to set the HttpStatus:
    when(responseSpecMock.getStatus()).thenReturn(HttpStatus.INTERNAL_SERVER_ERROR);
    
    • and for the onStatus, you can call the real method:
    when(responseSpecMock.onStatus(any(Predicate.class),any(Function.class)))
       .thenCallRealMethod();
    
    • This is the test class:
    package com.example.test;
    
    import org.junit.jupiter.api.Test;
    import org.junit.jupiter.api.extension.ExtendWith;
    import org.mockito.InjectMocks;
    import org.mockito.Mock;
    import org.mockito.junit.jupiter.MockitoExtension;
    import org.springframework.http.HttpStatus;
    import org.springframework.web.reactive.function.client.ClientResponse;
    import org.springframework.web.reactive.function.client.WebClient;
    import reactor.core.publisher.Mono;
    
    import java.util.function.Function;
    import java.util.function.Predicate;
    
    import static org.mockito.ArgumentMatchers.any;
    import static org.mockito.Mockito.when;
    
    
    @ExtendWith(MockitoExtension.class)
    public class ExperimentalWebClientTest {
    
        @Mock
        private WebClient webClientMock;
    
        @InjectMocks
        private ExperimentalWebClient experimentalWebClient;
    
        @Mock
        private WebClient.RequestHeadersUriSpec requestHeadersUriSpecMock;
    
        @Mock
        private WebClient.RequestHeadersSpec requestHeadersSpecMock;
    
        @Mock
        private CustomMinimalForTestResponseSpec responseSpecMock;
    
        @Test
        void shouldFailsWhenHttpStatus5xx() {
            //given
            when(webClientMock.get()).thenReturn(requestHeadersUriSpecMock);
            when(requestHeadersUriSpecMock.uri(any(Function.class)))
                .thenReturn(requestHeadersSpecMock);
            when(requestHeadersSpecMock.retrieve()).thenReturn(responseSpecMock);
    
            when(responseSpecMock.getStatus()).thenReturn(HttpStatus.INTERNAL_SERVER_ERROR);
    
            when(responseSpecMock.onStatus(any(Predicate.class), any(Function.class))).thenCallRealMethod();
    
            //when + Then
            assertThrows(MyCustomException.class,
                () -> experimentalWebClient.doStuff(),
                "call fails with Internal Server Error") ;
        }
    
    
        abstract class CustomMinimalForTestResponseSpec implements WebClient.ResponseSpec {
    
            public abstract HttpStatus getStatus();
    
            public WebClient.ResponseSpec onStatus(Predicate<HttpStatus> statusPredicate, Function<ClientResponse, Mono<? extends Throwable>> exceptionFunction) {
                if (statusPredicate.test(this.getStatus())) exceptionFunction.apply(ClientResponse.create(HttpStatus.OK).build()).block();
                return this;
            }
    
        }
    }