最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

spring boot - How to write Spock Test for RestClient that returns a RequestBodyUriSpec reponse - Stack Overflow

programmeradmin1浏览0评论

I am attempting to write a Spock test for testing a class which uses a Spring RestClient using a fluent api. However, I am unable to correctly mock all the parts and I am getting a NullPointerException. The code is very simple, it uses an injected RestClient like this:

 package com.scf.client;

 import com.scf.domain.model.ProductSearchRequest;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import .springframework.http.ResponseEntity;
 import .springframework.stereotype.Component;
 import .springframework.web.client.RestClient;
 import com.scf.domain.model.ProductCatalog;

 import static .springframework.http.MediaType.APPLICATION_JSON;

 @Slf4j
 @Component
 @RequiredArgsConstructor
 public class ProductRestClient {
    private final RestClient restClient;

    public ResponseEntity<ProductCatalog> findProduct(ProductSearchRequest request){
       return  restClient.post()
            .uri("/products/catalog")
            .contentType(APPLICATION_JSON)
            .body(request)
            .retrieve()
            .toEntity(ProductCatalog.class);
      }
 }

Here are the domain classes

package com.scf.domain.model;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

 @Data
 @NoArgsConstructor
 @AllArgsConstructor
 @Builder
 public class ProductCatalog {
     String productId;
     String productName;
     String productDescription;
 }
package com.scf.domain.model;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ProductSearchRequest {
     String catalogId;
     Boolean inStock;
}

Here is my attempt to write a test for the above:

package com.scf.client

import com.scf.domain.model.ProductCatalog
import com.scf.domain.model.ProductSearchRequest
import .springframework.web.client.RestClient
import spock.lang.Specification

class ProductRestClientSpec extends Specification {

    RestClient restClient = Mock()
    ProductRestClient productRestClient
    RestClient.RequestBodyUriSpec mockRequestBodyUriSpec = Mock(RestClient.RequestBodyUriSpec.class);

    def setup() {
        productRestClient = new ProductRestClient(restClient)
    }

    def "Should retrieve a Product Catalog for a valid request"() {
        given:
        def request = createProductSearchRequest()
        def response = createProductCatalogResponse()

        when:
        def result = productRestClient.findProduct(request)

        then:
        1 * restClient./post|uri|contentType|body|retrieve|onStatus|toEntity/(*_) >> mockRequestBodyUriSpec
        1 * mockRequestBodyUriSpec.get(_) >> { args -> response }
    }

    def createProductSearchRequest() {
        ProductSearchRequest.builder()
                .catalogId("XF-3333")
                .inStock(true)
                .build()
    }

    def createProductCatalogResponse() {
        ProductCatalog.builder()
                .productDescription("Ralph Lauren Fragrance")
                .productId("PRD-99W222")
                .productName("Ralph Cologne")
                .build()

    }
}

Unfortunately, this does not work. I get a null pointer exception. I want to be able to test getting the ProductCatalog from the ResponseEntity. How do I mock the RequestBodyUriSpec returned? I've been struggling with this for a day and a half, please help

I am attempting to write a Spock test for testing a class which uses a Spring RestClient using a fluent api. However, I am unable to correctly mock all the parts and I am getting a NullPointerException. The code is very simple, it uses an injected RestClient like this:

 package com.scf.client;

 import com.scf.domain.model.ProductSearchRequest;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import .springframework.http.ResponseEntity;
 import .springframework.stereotype.Component;
 import .springframework.web.client.RestClient;
 import com.scf.domain.model.ProductCatalog;

 import static .springframework.http.MediaType.APPLICATION_JSON;

 @Slf4j
 @Component
 @RequiredArgsConstructor
 public class ProductRestClient {
    private final RestClient restClient;

    public ResponseEntity<ProductCatalog> findProduct(ProductSearchRequest request){
       return  restClient.post()
            .uri("/products/catalog")
            .contentType(APPLICATION_JSON)
            .body(request)
            .retrieve()
            .toEntity(ProductCatalog.class);
      }
 }

Here are the domain classes

package com.scf.domain.model;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

 @Data
 @NoArgsConstructor
 @AllArgsConstructor
 @Builder
 public class ProductCatalog {
     String productId;
     String productName;
     String productDescription;
 }
package com.scf.domain.model;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ProductSearchRequest {
     String catalogId;
     Boolean inStock;
}

Here is my attempt to write a test for the above:

package com.scf.client

import com.scf.domain.model.ProductCatalog
import com.scf.domain.model.ProductSearchRequest
import .springframework.web.client.RestClient
import spock.lang.Specification

class ProductRestClientSpec extends Specification {

    RestClient restClient = Mock()
    ProductRestClient productRestClient
    RestClient.RequestBodyUriSpec mockRequestBodyUriSpec = Mock(RestClient.RequestBodyUriSpec.class);

    def setup() {
        productRestClient = new ProductRestClient(restClient)
    }

    def "Should retrieve a Product Catalog for a valid request"() {
        given:
        def request = createProductSearchRequest()
        def response = createProductCatalogResponse()

        when:
        def result = productRestClient.findProduct(request)

        then:
        1 * restClient./post|uri|contentType|body|retrieve|onStatus|toEntity/(*_) >> mockRequestBodyUriSpec
        1 * mockRequestBodyUriSpec.get(_) >> { args -> response }
    }

    def createProductSearchRequest() {
        ProductSearchRequest.builder()
                .catalogId("XF-3333")
                .inStock(true)
                .build()
    }

    def createProductCatalogResponse() {
        ProductCatalog.builder()
                .productDescription("Ralph Lauren Fragrance")
                .productId("PRD-99W222")
                .productName("Ralph Cologne")
                .build()

    }
}

Unfortunately, this does not work. I get a null pointer exception. I want to be able to test getting the ProductCatalog from the ResponseEntity. How do I mock the RequestBodyUriSpec returned? I've been struggling with this for a day and a half, please help

Share Improve this question edited Mar 23 at 2:52 kriegaex 67.8k15 gold badges120 silver badges224 bronze badges asked Mar 14 at 19:53 BreenDeenBreenDeen 7023 gold badges20 silver badges64 bronze badges 9
  • Could you provide the exception details? – Aleksander Burzec Commented Mar 17 at 9:06
  • Please create mini project for testing and share with us – Anish B. Commented Mar 17 at 13:28
  • The code I provided is the most can provide. The rest of the code is specific configuration for the RestClient. I have provided the complete model for the domain objects, the data is coming from a PostgreSQL database. It will be a lot of work to recreate that scenario. There is enough here to create a viable test – BreenDeen Commented Mar 17 at 17:17
  • I got same error on my local too. Trying to debug. Hopefully will find the problem sooner. – Anish B. Commented Mar 17 at 17:40
  • Are you facing this error? -> java.lang.NullPointerException: Cannot invoke ".springframework.web.client.RestClient$RequestBodyUriSpec.uri(String, Object[])" because the return value of ".springframework.web.client.RestClient.post()" is null. Just wanted to confirm – Anish B. Commented Mar 17 at 18:05
 |  Show 4 more comments

1 Answer 1

Reset to default 1 +50

Quoting my own comment:

In the call chain restClient.post().uri("/products/catalog").contentType(APPLICATION_JSON).body(request).retrieve().toEntity(ProductCatalog.class), the return types of each method are different, which means that you would have to mock and stub several more classes instead of always making your restClient return mockRequestBodyUriSpec.

How about something like this?

package de.scrum_master.stackoverflow.q79510107

import .springframework.http.HttpStatus
import .springframework.http.ResponseEntity
import .springframework.web.client.RestClient
import spock.lang.Specification

class ProductRestClientSpec extends Specification {
  RestClient.ResponseSpec responseSpec = Mock()
  RestClient.RequestBodyUriSpec requestBodyUriSpec = Stub() {
    /uri|contentType|body/(_) >> Stub(RestClient.RequestBodySpec) {
      retrieve() >> responseSpec
    }
  }
  RestClient restClient = Mock()
  ProductRestClient productRestClient = new ProductRestClient(restClient)

  def "Should retrieve a Product Catalog for a valid request"() {
    given:
    def request = createProductSearchRequest()
    def response = createProductCatalogResponse()

    when:
    def result = productRestClient.findProduct(request)

    then:
    1 * restClient.post() >> requestBodyUriSpec
    1 * responseSpec.toEntity(_) >> new ResponseEntity<ProductCatalog>(response, HttpStatus.OK)
    result.body == response
  }

  def createProductSearchRequest() {
    ProductSearchRequest.builder()
      .catalogId("XF-3333")
      .inStock(true)
      .build()
  }

  def createProductCatalogResponse() {
    ProductCatalog.builder()
      .productDescription("Ralph Lauren Fragrance")
      .productId("PRD-99W222")
      .productName("Ralph Cologne")
      .build()
  }

}

Or if you can do without over-specifying the test, checking which methods are called how often, simplify to:

package de.scrum_master.stackoverflow.q79510107

import .springframework.http.HttpStatus
import .springframework.http.ResponseEntity
import .springframework.web.client.RestClient
import spock.lang.Specification

class ProductRestClientSpec extends Specification {
  RestClient.ResponseSpec responseSpec = Mock()
  RestClient restClient = Stub() {
    post() >> Stub(RestClient.RequestBodyUriSpec) {
      /uri|contentType|body/(_) >> Stub(RestClient.RequestBodySpec) {
        retrieve() >> responseSpec
      }
    }
  }
  ProductRestClient productRestClient = new ProductRestClient(restClient)

  def "Should retrieve a Product Catalog for a valid request"() {
    given:
    def request = createProductSearchRequest()
    def response = createProductCatalogResponse()

    and:
    responseSpec.toEntity(_) >> new ResponseEntity<ProductCatalog>(response, HttpStatus.OK)

    expect:
    productRestClient.findProduct(request).body == response
  }

  def createProductSearchRequest() {
    ProductSearchRequest.builder()
      .catalogId("XF-3333")
      .inStock(true)
      .build()
  }

  def createProductCatalogResponse() {
    ProductCatalog.builder()
      .productDescription("Ralph Lauren Fragrance")
      .productId("PRD-99W222")
      .productName("Ralph Cologne")
      .build()
  }

}

Try it in the Groovy Web Console. As you can see there, it is a complete, minimal reproducer which does not even need Lombok, because the helper classes referenced by your application and test code are implemented using Groovy annotations like @Canonical and @Builder. It also uses @Grab(group='.springframework', module='spring-web', version='6.2.5'), so the application code can really use Spring and we are not testing dummy classes.

发布评论

评论列表(0)

  1. 暂无评论