Robert Strauch
Robert Strauch

Reputation: 12896

How to extract values from a Spring WebTestClient JSON response for usage in subsequent requests?

With Spring Boot 2.4.2 I'm using the WebTestClient to invoke requests in my integration tests.

This is the first request which gets a list of messages:

webTestClient
    .get()
    .uri("/api/messages")
    .headers(http -> http.setBearerAuth(token))
    .exchange()
    .expectStatus().isOk()
    .expectHeader().contentType(APPLICATION_JSON)
    .expectBody()
    .jsonPath("$.length()").isEqualTo(1)
    .jsonPath("$[0].id").isNumber()
    .jsonPath("$[0].type").isEqualTo(4);

Now I'd like to invoke a subsequent request to download a specific message. For this, I need the id which was already checked with jsonPath("$[0].id").

webTestClient
    .get()
    .uri(uriBuilder -> uriBuilder.path("/api/messages/{id}").build(extractedId))
    .headers(http -> http.setBearerAuth(token))
    .exchange()
    .expectStatus().isOk();

How can I extract this id into a local variable or else, so that it's available for the second request?

Upvotes: 5

Views: 19748

Answers (3)

ynovytskyy
ynovytskyy

Reputation: 448

Use value(Consumer<T> consumer) on .jsonPath("<path>") result and "consume" the value of the JSON field into a variable.

Kotlin code below:

var testId = ""
webClient.post()
  .uri("/api/test")
  .contentType(MediaType.APPLICATION_JSON)
  .body(BodyInserters.fromValue(testPostBodyString))
  .accept(MediaType.APPLICATION_JSON)
  .exchange()
  .expectStatus().is2xxSuccessful
  .expectBody()
  .jsonPath("$.testId").isNotEmpty()
  .jsonPath("$.testId").value<String> { testId = it}

Upvotes: 1

Tamas K
Tamas K

Reputation: 96

I encountered the same problem, and this is the solution I came up with.

According to documentation,

WebTestClient is a thin shell around WebClient, using it to perform requests and exposing a dedicated, fluent API for verifying responses.

To my understanding it is geared for testing the response in a similar fashion as assertThat assertations do.

In my solution I instead use WebClient for extracting values.

The code snippet below should explain all the details. Note that it is just a generalized example, you should tailor it for your needs.

import org.json.JSONException;
import org.json.JSONObject;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;

import static org.springframework.http.MediaType.APPLICATION_JSON;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class FooTest {

    @Autowired
    private WebTestClient webTestClient;

    /**
     * The port of the server. It starts on a RANDOM_PORT. @LocalServerPort is a way to find out what this port is.
     */
    @LocalServerPort
    private int port;

    @Test
    void someTestMethod() throws JSONException {


        // Create the request body that we'll send with the POST request.
        String postRequestBody = new JSONObject()
                .put("JsonField_1", "value a")
                .put("JsonFiled_2", "value b")
                // .put("any_additional_json_fields", "with_any_values")
                .toString();

        // The URI where we'll send the request to.
        String postRequestUri = "http://localhost:" + String.valueOf(port) + "/some_api";

        // Send a POST request, and save the response.
        TypeOfResponseWeExpect response = WebClient.create()
                .post()
                .uri(postRequestUri)
                .contentType(APPLICATION_JSON)
                .accept(APPLICATION_JSON)
                .body(BodyInserters.fromValue(postRequestBody))
                .retrieve()
                .bodyToMono(TypeOfResponseWeExpect.class)
                .block();

        // And now we can extract any values from the response.
        long extractedId = response.getId();
        String token = response.getToken();
        FooField fooField = response.getFoo();
        BarField barField = response.getBar();

        // Now we can use the extracted id field, or any field from the response.
        webTestClient
                .get()
                .uri(uriBuilder -> uriBuilder.path("/api/messages/{id}").build(extractedId))
                .headers(http -> http.setBearerAuth(token))
                .exchange()
                .expectStatus().isOk();
    }
}

Edit: After some further tries, I found a way to extract the response with WebTestClient too:

TypeOfResponseWeExpect response = this.webTestClient
        .post()
        .uri(postRequestUri)
        .contentType(APPLICATION_JSON)
        .accept(APPLICATION_JSON)
        .body(BodyInserters.fromValue(postRequestBody))
        .exchange()
        .expectBody(TypeOfResponseWeExpect.class)
        .returnResult()
        .getResponseBody();

Upvotes: 2

Lucas Campos
Lucas Campos

Reputation: 1920

You can check their official docs for it.

But expanding the answer a bit, easiest way would be this

val result = webTestClient
                .get()
                .uri(uriBuilder -> uriBuilder.path("/api/messages/{id}").build(extractedId))
                .headers(http -> http.setBearerAuth(token))
                .exchange()
                .expectStatus().isOk()
                .returnResult();

There are also ways to get an (infinite) stream of responses explained on docs, which is pretty similar to the example above.

Upvotes: 6

Related Questions