Tokoro-San
Tokoro-San

Reputation: 45

Spring WebClient - Mockito NullPointerException body() is null

I am currently testing a Service performing HTTP requests with WebClient on Spring. When I'm mocking the service I get a NullPointerException on my body when I'm testing a method which performs a POST request. Here is the error :

java.lang.NullPointerException: Cannot invoke "org.springframework.web.reactive.function.client.WebClient$RequestHeadersSpec.retrieve()" because the return value of "org.springframework.web.reactive.function.client.WebClient$RequestBodySpec.body(org.reactivestreams.Publisher, java.lang.Class)" is null

Here is my code :

import com.bluelagoon.payetonkawa.dolibarr.entities.input.LoginEntity;
import com.bluelagoon.payetonkawa.dolibarr.entities.output.SuccessEntity;
import com.bluelagoon.payetonkawa.dolibarr.entities.output.UserInfoEntity;
import com.bluelagoon.payetonkawa.dolibarr.services.DolibarrInfraService;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

@Service
public class DolibarrInfraServiceImpl implements DolibarrInfraService {

    private final WebClient webClient;

    public DolibarrInfraServiceImpl(WebClient webClient) {
        this.webClient = webClient;
    }

    @Override
    public SuccessEntity login(LoginEntity login) {
        return webClient.post()
                .uri("/login")
                .body(Mono.just(login), LoginEntity.class)
                .retrieve()
                .bodyToMono(SuccessEntity.class)
                .block();
    }

   ...
}

Here is my Test class :

package com.bluelagoon.payetonkawa.dolibarr.impl;

import com.bluelagoon.payetonkawa.dolibarr.entities.input.LoginEntity;
import com.bluelagoon.payetonkawa.dolibarr.entities.output.SuccessEntity;
import com.bluelagoon.payetonkawa.dolibarr.entities.output.TokenEntity;
import com.bluelagoon.payetonkawa.dolibarr.services.DolibarrInfraService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentMatchers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

import java.net.http.HttpRequest;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.lenient;

@ExtendWith(MockitoExtension.class)
class DolibarrInfraServiceImplTest {

    @Mock
    private WebClient webClientMock;

    @Mock
    private WebClient.RequestBodyUriSpec requestBodyUriSpecMock;

    @Mock
    private WebClient.RequestBodySpec requestBodySpecMock;

    @Mock
    private WebClient.RequestHeadersUriSpec requestHeadersUriSpecMock;

    @Mock
    private WebClient.RequestHeadersSpec requestHeadersSpecMock;

    @Mock
    private WebClient.ResponseSpec responseSpecMock;

    @InjectMocks
    private DolibarrInfraServiceImpl dolibarrInfraService;

    @Test
    void loginTest_should_return_a_valid_SuccessEntity(){
        var loginEntity = new LoginEntity();
        loginEntity.setLogin("test");
        loginEntity.setPassword("test");

        var tokenEntity = new TokenEntity();
        tokenEntity.setCode(200);
        tokenEntity.setToken("test");

        var successEntity = new SuccessEntity();
        successEntity.setSuccess(tokenEntity);

        when(webClientMock.post())
                .thenReturn(requestBodyUriSpecMock);

        when(requestBodyUriSpecMock.uri(anyString()))
                .thenReturn(requestBodySpecMock);

        when(requestBodySpecMock.body(Mono.just(loginEntity), LoginEntity.class))
                .thenReturn(requestHeadersSpecMock);

        when(requestHeadersSpecMock.retrieve())
                .thenReturn(responseSpecMock);

        when(responseSpecMock.bodyToMono(ArgumentMatchers.<Class<SuccessEntity>>notNull()))
                .thenReturn(Mono.just(successEntity));


        var expectedTokenEntity = new TokenEntity();
        expectedTokenEntity.setToken("test");
        expectedTokenEntity.setCode(200);

        var expectedSuccessEntity = new SuccessEntity();
        expectedSuccessEntity.setSuccess(expectedTokenEntity);

        var resultSuccessEntity = dolibarrInfraService.login(loginEntity);

        assertEquals(expectedSuccessEntity, resultSuccessEntity);
    }


}

Am I missing something?

Thank you for your answers

Upvotes: 0

Views: 9940

Answers (1)

mfaisalhyder
mfaisalhyder

Reputation: 2284

For those who stumble upon this thread and finding hard time to mock any downstream calls using webClient, I hope this will help in someway.

Actual code which calls the downstream:

public Mono<String> getSomething(final MyContext myContext) {
    return myCustomWebClient
            .get()
            .uri("/my/downstream")
            .headers(headers -> {
                headers.setBearerAuth(myContext.jwtToken());
                headers.set("header1", myContext.getHeader1());
            })
            .retrieve()
            .bodyToMono(String.class));
}

Now here is my TestClass to test this piece of code from: Router > Handler > Any Other layer > WebClient

@TestPropertySource(properties = {
        "spring.config.location=classpath:application-test.yml",
        "spring.profiles.active=your-profile"
})
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class MyClassTest {

    @Autowired
    protected WebTestClient webTestClient;

    @MockBean
    private WebClient myCustomWebClient;

    @MockBean
    private WebClient.ResponseSpec responseSpec;

    @MockBean
    @SuppressWarnings("rawtypes")
    private WebClient.RequestHeadersUriSpec requestHeadersUriSpec;

    @SuppressWarnings("rawtypes")
    private WebClient.RequestHeadersSpec requestHeadersSpec;

    @Test
    @SuppressWarnings("unchecked")
    void testCustomMethod() {
        Mockito.when(requestHeadersSpec.headers(Mockito.any()))
        .thenReturn(requestHeadersSpec);

        Mockito.when(requestHeadersSpec.retrieve())
        .thenReturn(responseSpec);

       Mockito.when(myCustomWebClient.get())
       .thenReturn(requestHeadersUriSpec);

       Mockito.when(requestHeadersUriSpec.uri("/my/downstream"))
       .thenReturn(requestHeadersSpec);

       Mockito.when(responseSpec.bodyToMono(String.class))
       .thenReturn(Mono.empty());

       Mockito.when(myCustomWebClient.get().uri("/my/downstream")
       .headers(Mockito.any()).retrieve().bodyToMono(String.class))
       .thenReturn(Mono.empty());

        webTestClient.post()
            .uri("/your-end-point")
            .accept(MediaType.APPLICATION_JSON)
            .contentType(MediaType.APPLICATION_JSON)
            .header("header1", "header1Value")
            .bodyValue(YourRequestClass.builder().build())
            .exchange()
            .expectStatus()
            .is5xxServerError()
            .expectBody(YourErrorClassPojo.class)
            .value(yourErrorResponsePojo -> {
                // Assertions.assertEquals();
            })
            .consumeWith(System.out::println);
}

Upvotes: 0

Related Questions