簡體   English   中英

Custom-Webclient Spock 測試在 GET 調用中拋出不需要的 NullPointerException

[英]Custom-Webclient Spock test throws unwanted NullPointerException in GET call

使用下面的自定義 WebClient:

@Slf4j
@RequiredArgsConstructor
@Component
public class TransitApiClient {

    private final TransitApiClientProperties transitApiClientProperties;

    private final WebClient transitApiWebClient;

    private final OAuth2CustomClient oAuth2CustomClient;

    public ResponseEntity<Void> isOfficeOfTransitValidAndNational(String officeId){
        try {
            final String url = UriComponentsBuilder.fromUriString(transitApiClientProperties.getFindOfficeOfTransit())
                    .queryParam("codelistKey", "CL173")
                    .queryParam("itemCode", officeId)
                    .build()
                    .toUriString();

            return transitApiWebClient.get()
                    .uri(url)
                    .header(AUTHORIZATION, getAccessTokenHeaderValue(oAuth2CustomClient.getJwtToken()))
                    .retrieve()
                    .onStatus(status -> status.value() == HttpStatus.NO_CONTENT.value(),
                            clientResponse -> Mono.error( new InvalidOfficeException(null,
                                    "Invalid Office exception occurred while invoking :" + transitApiClientProperties.getFindOfficeOfTransit() + officeId)))
                    .toBodilessEntity()
                    .block();

        } catch (WebClientResponseException webClientResponseException) {
            log.error("Technical exception occurred while invoking :" + transitApiClientProperties.getFindOfficeOfTransit(), webClientResponseException);
            throw new TechnicalErrorException(null, "Technical exception occurred while trying to find " + transitApiClientProperties.getFindOfficeOfTransit(), webClientResponseException);
        }
    }

其預期用途是命中一個端點,並檢查它是否返回帶有正文的 200 代碼或 204 NoContent 代碼,並對一些自定義異常做出相應的反應。

我已經在下面實現了 groovy-spock 測試:

class TransitApiClientSpec extends Specification {

    private WebClient transitApiWebClient
    private TransitApiClient transitApiClient
    private OAuth2CustomClient oAuth2CustomClient
    private TransitApiClientProperties transitApiClientProperties
    private RequestBodyUriSpec requestBodyUriSpec
    private RequestHeadersSpec requestHeadersSpec
    private RequestBodySpec requestBodySpec
    private ResponseSpec responseSpec
    private RequestHeadersUriSpec requestHeadersUriSpec

    def setup() {
        transitApiClientProperties = new TransitApiClientProperties()
        transitApiClientProperties.setServiceUrl("https://test-url")
        transitApiClientProperties.setFindOfficeOfTransit("/transit?")
        transitApiClientProperties.setUsername("username")
        transitApiClientProperties.setPassword("password")
        transitApiClientProperties.setAuthorizationGrantType("grantType")
        transitApiClientProperties.setClientId("clientId")
        transitApiClientProperties.setClientSecret("clientSecret")

        oAuth2CustomClient = Stub(OAuth2CustomClient)
        oAuth2CustomClient.getJwtToken() >> "token"
        transitApiWebClient = Mock(WebClient)
        requestHeadersSpec = Mock(RequestHeadersSpec)
        responseSpec = Mock(ResponseSpec)
        requestHeadersUriSpec = Mock(RequestHeadersUriSpec)
        transitApiClient = new TransitApiClient(transitApiClientProperties, transitApiWebClient, oAuth2CustomClient)
    }

    def "request validation of OoTra and throw InvalidOfficeException"(){
        given :
        def officeId = "testId"
        def uri = UriComponentsBuilder
                .fromUriString(transitApiClientProperties.getFindOfficeOfTransit())
                .queryParam("codelistKey", "CL173")
                .queryParam("itemCode", officeId)
                .build()
                .toUriString()

        1 * transitApiWebClient.get() >> requestHeadersUriSpec
        1 * requestHeadersUriSpec.uri(uri) >> requestHeadersSpec
        1 * requestHeadersSpec.header(HttpHeaders.AUTHORIZATION, "Bearer token") >> requestHeadersSpec
        1 * requestHeadersSpec.retrieve() >> responseSpec
        1 * responseSpec.onStatus() >> Mono.error( new InvalidOfficeException(null,null) )

        when :
        def response = transitApiClient.isOfficeOfTransitValidAndNational(officeId)

        then :
        thrown(InvalidOfficeException)
    }

但不是拋出InvalidOfficeException ,而是拋出java.lang.NullPointerException

它似乎在測試運行期間被觸發,程序調用以下內容:


return transitApiWebClient.get()
     .uri(url)
     .header(AUTHORIZATION, getAccessTokenHeaderValue(oAuth2CustomClient.getJwtToken()))
     .retrieve()
     .onStatus(status -> status.value() == HttpStatus.NO_CONTENT.value(),
                            clientResponse -> Mono.error( new InvalidOfficeException(null,
                                    "Invalid Office exception occurred while invoking :" + transitApiClientProperties.getFindOfficeOfTransit() + officeId)))
     .toBodilessEntity()   <---------------------- **HERE**
     .block();

我知道我沒有嘲笑它的行為,但在我看來,其他一些模擬沒有正確完成。

我只能建議不要模擬WebClient調用,因為模擬的必要步驟很痛苦,正如您自己所見,需要大量中間模擬而實際上並沒有增加太多價值。 這基本上重復執行,從而鎖定它,這不是一件好事。

我通常做的是將所有與WebClient交互的代碼提取到一個客戶端class中,並且在我的代碼中只模擬這個class交互。 從外觀上看,這就是您已經在使用TransitApiClient所做的事情。 對於這些客戶端類,我建議使用MockServerWireMock或任何其他框架對其進行測試。 通過這種方式,您實際上可以確保發送/接收到正確的請求/響應,並且您不必笨拙地處理WebClient接口。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM