liveBook · Manning

WebTestClient

As explained in Spring in Action, Sixth Edition

WebTestClient is a Spring reactive testing client for exercising HTTP endpoints and verifying their responses without starting a server.

WebTestClient is a Spring WebFlux testing client for exercising reactive HTTP controllers and checking their responses without starting a server. Bind it directly to a controller for focused tests, or autowire it against a running Spring Boot application for integration tests. section 12.3 section 12.3.3

A typical test follows this sequence:

  • Create test data with Flux, Mono, or helper methods, then mock the repository when testing only the controller. section 12.3.1 section 12.3.2
  • Build a client with WebTestClient.bindToController(new TacoController(repository)).build(). section 12.3.1
  • Describe a request with get(), post(), put(), patch(), delete(), or head(), provide its URI and body when needed, and call exchange(). section 12.3.2
  • Assert status codes and response content with expectStatus(), jsonPath(), expectBody(), json(), or expectBodyList(). section 12.3.1 section 12.3.2

Testing a taco creation endpoint

A focused POST test sends an application/json Taco and verifies that the controller returns HTTP 201 with the saved Taco: testClient.post().uri("/api/tacos").contentType(MediaType.APPLICATION_JSON).body(unsavedTacoMono, Taco.class).exchange().expectStatus().isCreated().expectBody(Taco.class).isEqualTo(savedTaco). section 12.3.2

Listing 12.1 Using WebTestClient to test TacoController
package tacos.web.api;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import tacos.Ingredient;
import tacos.Ingredient.Type;
import tacos.Taco;
import tacos.data.TacoRepository;
 
public class TacoControllerTest {
 
  @Test
  public void shouldReturnRecentTacos() {
    Taco[] tacos = {
        testTaco(1L), testTaco(2L),
        testTaco(3L), testTaco(4L),                                #1
        testTaco(5L), testTaco(6L),
        testTaco(7L), testTaco(8L),
        testTaco(9L), testTaco(10L),
        testTaco(11L), testTaco(12L),
        testTaco(13L), testTaco(14L),
        testTaco(15L), testTaco(16L)};
    Flux<Taco> tacoFlux = Flux.just(tacos);
 
    TacoRepository tacoRepo = Mockito.mock(TacoRepository.class);
    when(tacoRepo.findAll()).thenReturn(tacoFlux);                 #2
 
    WebTestClient testClient = WebTestClient.bindToController(
        new TacoController(tacoRepo))
        .build();                                                  #3
 
    testClient.get().uri("/api/tacos?recent")
      .exchange()                                                  #4
      .expectStatus().isOk()                                       #5
      .expectBody()
        .jsonPath("$").isArray()
        .jsonPath("$").isNotEmpty()
        .jsonPath("$[0].id").isEqualTo(tacos[0].getId().toString())
        .jsonPath("$[0].name").isEqualTo("Taco 1")
        .jsonPath("$[1].id").isEqualTo(tacos[1].getId().toString())
        .jsonPath("$[1].name").isEqualTo("Taco 2")
        .jsonPath("$[11].id").isEqualTo(tacos[11].getId().toString())
        .jsonPath("$[11].name").isEqualTo("Taco 12")
        .jsonPath("$[12]").doesNotExist();
  }
 
  ...
 
}

Use the complete controller test listing to see mock repository setup, generated Taco fixtures, client binding, request execution, and response assertions together. section 12.3.1

package tacos;
 
import java.io.IOException;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.reactive.server.WebTestClient;
 
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT)
public class TacoControllerWebTest {
  @Autowired
  private WebTestClient testClient;
 
}

Use the integration-test listing to see Spring Boot RANDOM_PORT setup and WebTestClient autowiring for requests against a live server. section 12.3.3

How Spring in Action, Sixth Edition covers it

How deep it goes
Covers focused controller tests and live-server integration tests, but does not explain WebTestClient internals.
Examples
Uses TacoController GET and POST tests, mocked TacoRepository data, JSON fixtures, JSONPath expressions, and RANDOM_PORT integration tests.
What it assumes
Assumes familiarity with Spring WebFlux controllers and JUnit tests while demonstrating mocked repositories and reactive test data.
What you can do after
Apply it via WebTestClient to verify reactive endpoint status codes, JSON payloads, typed bodies, and HTTP method behavior.
How it connects
Connects reactive controllers with Spring WebFlux, JUnit, Mockito, Flux, Mono, JSONPath, and WebClient.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Spring in Action, Sixth Edition. The section links point to the parts of the book it draws on.