Writing tests

Module 5. Spring
Level 12 , Lesson 1
Available

WebTestClient provides an API identical to WebClient, up to the execution of the request with using exchange(). For examples of preparing a request with any content, including form data, multipart data, and more, see the documentation for WebClient.

After exchange() is called, WebTestClient diverges from WebClient and instead continues the response validation workflow .

To confirm response status and headers, use the following:

Java

client.get().uri("/persons/1")
    .accept(MediaType.APPLICATION_JSON)
    .exchange()
    .expectStatus().isOk()
    .expectHeader().contentType(MediaType.APPLICATION_JSON);
Kotlin

client.get().uri("/persons/1")
    .accept(MediaType.APPLICATION_JSON)
    .exchange()
    .expectStatus().isOk()
    .expectHeader().contentType(MediaType.APPLICATION_JSON)

If you want all expected events to be acknowledged, even if one of them fails, you can use expectAll(..) instead of several consecutive calls to expect*(..). This feature is similar to soft assertions support in AssertJ and assertAll() in JUnit Jupiter.

Java

client.get().uri("/persons/1")
    .accept(MediaType.APPLICATION_JSON)
    .exchange()
    .expectAll(
        spec -> spec.expectStatus().isOk(),
        spec -> spec.expectHeader().contentType(MediaType.APPLICATION_JSON)
    );

You can then choose to decode the response body using one of the following methods:

  • expectBody(Class<T>): Decoding into one object.

  • expectBodyList(Class<T>) : Decoding and collecting objects into List<T>.

  • expectBody(): Decoding into byte[] for JSON content or an empty body.

And execute assertions on the resulting higher-level object(s):

Java

client.get().uri("/persons")
        .exchange()
        .expectStatus().isOk()
        .expectBodyList(Person.class).hasSize(3).contains(person);
Kotlin

import org.springframework.test.web.reactive.server.expectBodyList
client.get().uri("/persons")
        .exchange()
        .expectStatus().isOk()
        .expectBodyList<Person>().hasSize(3).contains(person)

If the built-in assertions are not enough, you can use them instead this object and execute any other statements:

Java

import org.springframework.test.web.reactive.server.expectBody
client.get().uri("/persons/1")
        .exchange()
        .expectStatus().isOk()
        .expectBody(Person.class)
        .consumeWith(result -> {
            // special statements (eg AssertJ)...
        });
Kotlin

client.get().uri("/persons/1")
        .exchange()
        .expectStatus().isOk()
        .expectBody<Person>()
        .consumeWith {
            // special statements (eg AssertJ)...
        }

Or you can exit the workflow and get EntityExchangeResult:

Java
 
EntityExchangeResult<Person> result = client.get().uri("/persons/1")
        .exchange()
        .expectStatus().isOk()
        .expectBody(Person.class)
        .returnResult();
Kotlin

import org.springframework.test.web.reactive.server.expectBody
val result = client.get().uri("/persons/1")
        .exchange()
        .expectStatus().isOk
        .expectBody<Person>()
        .returnResult()
If you need to decode to the target type using generics, look for overloaded methods that accept ParameterizedTypeReference instead of Class<T>.

No content

If the response is expected to have no content, then you can confirm this as follows:

Java
 
client.post().uri("/persons")
        .body(personMono, Person.class)
        .exchange()
        .expectStatus().isCreated()
        .expectBody().isEmpty();
Kotlin

client.post().uri("/persons")
        .bodyValue(person)
        .exchange()
        .expectStatus().isCreated()
        .expectBody().isEmpty()

If you want to ignore the contents of the response, then below is the release of the contents without any assertions :

Java

client.get().uri("/persons/123")
        .exchange()
        .expectStatus().isNotFound()
        .expectBody(Void.class);
Kotlin

client.get().uri("/persons/123")
        .exchange()
        .expectStatus().isNotFound
        .expectBody<Unit>()

JSON content

You can use expectBody() without a target type to make assertions on unformatted content, rather than having to do it through the higher-level object(s).

To examine the full JSON content with using JSONAssert:

Java

client.get().uri("/persons/1")
        .exchange()
        .expectStatus().isOk()
        .expectBody()
        .json("{\"name\":\"Jane\"}")
Kotlin

client.get().uri("/persons/1")
        .exchange()
        .expectStatus().isOk()
        .expectBody()
        .json("{\"name\":\"Jane\"}")

To check the content JSON using JSONPath:

Java

client.get().uri("/persons")
        .exchange()
        .expectStatus().isOk()
        .expectBody()
        .jsonPath("$[0].name").isEqualTo("Jane")
        .jsonPath("$[1].name").isEqualTo("Jason");
Kotlin

client.get().uri("/persons")
        .exchange()
        .expectStatus().isOk()
        .expectBody()
        .jsonPath("$[0].name").isEqualTo("Jane")
        .jsonPath("$[1].name").isEqualTo("Jason")

Stream responses

To check for potentially infinite streams , such as "text/event-stream" or "application/x-ndjson", start by checking the response status and headers, and then get FluxExchangeResult:

Java

FluxExchangeResult<MyEvent> result = client.get().uri("/events")
        .accept(TEXT_EVENT_STREAM)
        .exchange()
        .expectStatus().isOk()
        .returnResult(MyEvent.class);
        
Kotlin

import org.springframework.test.web.reactive.server.returnResult
val result = client.get().uri("/events")
        .accept(TEXT_EVENT_STREAM)
        .exchange()
        .expectStatus().isOk()
        .returnResult<MyEvent>()

You are now ready to consume the response stream using StepVerifier from reactor-test:

Java

Flux<Event> eventFlux = result.getResponseBody();
StepVerifier.create(eventFlux)
        .expectNext(person)
        .expectNextCount(4)
        .consumeNextWith(p -> ...)
        .thenCancel()
        .verify();
Kotlin

val eventFlux = result.getResponseBody()
StepVerifier.create(eventFlux)
        .expectNext(person)
        .expectNextCount(4)
        .consumeNextWith { p -> ... }
        .thenCancel()
        .verify()

MockMvc Assertions

WebTestClient is HTTP client, so it can only check what's in the client response, including status, headers, and body.

When testing a Spring MVC application with a MockMvc server setup, you have the additional ability to perform further assertions on the server response .To do this, start by getting the ExchangeResult after adding the assertion to the body:

Java

// For a response with body
EntityExchangeResult<Person> result = client.get().uri("/persons/1")
        .exchange()
        .expectStatus().isOk()
        .expectBody(Person.class)
        .returnResult();
// For a response without a body
EntityExchangeResult<Void> result = client.get().uri("/path")
        .exchange()
        .expectBody().isEmpty();
Kotlin

// For a response with a body
val result = client.get().uri("/persons/1")
        .exchange()
        .expectStatus().isOk()
        .expectBody(Person.class)
        .returnResult();
// For a response without a body
val result = client.get().uri("/path")
        .exchange()
        .expectBody().isEmpty();

Then switch to MockMvc server response assertions:

Java

MockMvcWebTestClient.resultActionsFor(result)
        .andExpect(model().attribute("integer", 3))
        .andExpect(model().attribute("string", "a string value"));
Kotlin

MockMvcWebTestClient.resultActionsFor(result)
        .andExpect(model().attribute("integer", 3))
        .andExpect(model().attribute("string", "a string value"));
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION