How to fetch response using WebTestClient

6k views Asked by At

I am new to Reactive programming and having trouble testing. I have a very simple scenario,

an Entity:

class SimpleEntity{
  @Id    
  int id;
  String name;
}

a related repository:

class SimpleEntityRepository extends JpaRepository<SimpleEntity, Integer>{

  Slice<SimpleEntity> findByName(String name, Pageable pageable);

}

a related service:

class SimpleEntityService{
  @Autowired
  SimpleEntityRepository repository;

  public Mono<Slice<SimpleEntity>> findByName(String name, Pageable pageable){
    //All business logic excluded
    return Mono.just(
      repository.findByName(name, pageable);
    );
  }

}

a related controller:

class SimpleEntityController{

  @Autowired
  SimpleEntityService service;
  
  @RequestMapping("/some-mapping")
  public Mono<Slice<SimpleEntity>> findByName(@Requestparam String name, int pageNum){
    return service.findByName(name, Pageable.of(pageNum, 100));
  }

}

Now, in my integrations tests, I am trying hit the controller using WebTestClient but I am unable to understand how can I fetch and deserialize the response:

@Test
public void someIntegrationTest(){
     WebTestClient.ResponseSpec responseSpec = webTestClient.get()
          .uri(URI)
          .accept(MediaType.APPLICATION_JSON)
          .exchange();
    responseSpec.returnResult(SliceImpl.class).getResponseBody.blockFirst();
} 

The last line throws the following exception:

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of org.springframework.data.domain.Pageable (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information at [Source: UNKNOWN; byte offset: #UNKNOWN] (through reference chain: org.springframework.data.domain.SliceImpl["pageable"])

What I essentially want is to be able to get the Slice and be able to perform assertions over it.

3

There are 3 answers

0
Alex On

There are several questions here

  1. To deserialize generic type use ParameterizedTypeReference.
  2. The you could use WebTestClient API to validate response and it's not necessary to block. For example value or consumeWith allows to access the body and assert result.
WebTestClient.get()
        .uri("/some-mapping")
        .exchange()
        .expectStatus().isOk()
        .expectBody(new ParameterizedTypeReference<Slice<SimpleEntity>>() {})
        .value(slice -> {
            assertEquals(...);
        });
2
Ricard Kollcaku On

After you do exchange you can do expectBody or expectBodyList based on your response if is list or object and than you have functions like contain etc..

webClient
    .get()
    .uri("your url here")
    .contentType(MediaType.APPLICATION_JSON)
    .exchange()
    .expectStatus()
    .isOk()
    .expectBodyList(YOUROBJECT.class)
    .contains(object that you expect)
0
Newbie On

In 2022 versions of spring boot, in the event of a failure you will see the request and response as part of your assertion failure logs. If you are interested in logging the request and response in the case where your assertions don't fail, you can consume the result of the client call as shown below(in Kotlin).

webTestClient
.get()
.uri("/something")
.exchange()
.expectStatus().isOk
.returnResult<String>() //Generic type doesn't matter in this example
.consumeWith { logger.info { it } }

This works by "exiting" the chain via returnResult and using consumeWith to access ExchangeResult.toString() which conveniently prints the request and response.