Greg7000
Greg7000

Reputation: 425

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:

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.

Upvotes: 3

Views: 11011

Answers (2)

amekki
amekki

Reputation: 106

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;
        }

    }
}

Upvotes: 7

Greg7000
Greg7000

Reputation: 425

I think the answer to my question is that Mockito is not the right tool to test such a thing. Using wiremock seems like the convenient approach

When I test using mainly the following libraries :

  • import com.github.tomakehurst.wiremock.client.WireMock;
  • import org.springframework.test.context.junit4.SpringRunner;
  • import org.springframework.boot.test.context.SpringBootTest;
  • import org.springframework.test.web.reactive.server.WebTestClient;

I managed to get the job done.

If you are facing a similar issue, I recommend you take a look at https://www.sudoinit5.com/post/spring-boot-testing-consumer/, the provided examples are similar to what I have done to accomplish my tests.

However, I am still interested if anyone knows a way to accomplish the question with Mockito.

Upvotes: 0

Related Questions