CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-springframework-boot--spring-boot-starter-webflux

Starter for building WebFlux applications using Spring Framework's Reactive Web support

Pending
Overview
Eval results
Files

testing.mddocs/

Testing

Spring Boot WebFlux provides comprehensive testing support through WebTestClient and reactive testing utilities. This enables full integration testing of reactive web applications with support for mocking, assertions, and test-specific configurations.

WebTestClient Interface

Core WebTestClient

public interface WebTestClient {
    
    RequestHeadersUriSpec<?> get();
    RequestBodyUriSpec post();
    RequestBodyUriSpec put();
    RequestBodyUriSpec patch();
    RequestHeadersUriSpec<?> delete();
    RequestHeadersUriSpec<?> options();
    RequestHeadersUriSpec<?> head();
    
    RequestBodyUriSpec method(HttpMethod method);
    
    Builder mutate();
    
    static Builder bindToServer();
    static Builder bindToController(Object... controllers);
    static Builder bindToApplicationContext(ApplicationContext applicationContext);
    static Builder bindToRouterFunction(RouterFunction<?> routerFunction);
    static Builder bindToWebHandler(WebHandler webHandler);
    static Builder bindToWebServer(int port);
    
    interface Builder {
        Builder baseUrl(String baseUrl);
        Builder defaultHeader(String header, String... values);
        Builder defaultHeaders(Consumer<HttpHeaders> headersConsumer);
        Builder defaultCookie(String cookie, String... values);
        Builder defaultCookies(Consumer<MultiValueMap<String, String>> cookiesConsumer);
        Builder filter(ExchangeFilterFunction filter);
        Builder filters(Consumer<List<ExchangeFilterFunction>> filtersConsumer);
        Builder clientConnector(ClientHttpConnector connector);
        Builder codecs(Consumer<ClientCodecConfigurer> configurer);
        Builder exchangeStrategies(ExchangeStrategies strategies);
        Builder responseTimeout(Duration timeout);
        
        WebTestClient build();
    }
}

Request Specification Interfaces

RequestHeadersUriSpec

public interface RequestHeadersUriSpec<S extends RequestHeadersSpec<S>> extends UriSpec<S> {
    
}

public interface RequestHeadersSpec<S extends RequestHeadersSpec<S>> {
    S header(String headerName, String... headerValues);
    S headers(Consumer<HttpHeaders> headersConsumer);
    S accept(MediaType... acceptableMediaTypes);
    S acceptCharset(Charset... acceptableCharsets);
    S cookie(String name, String value);
    S cookies(Consumer<MultiValueMap<String, String>> cookiesConsumer);
    S ifModifiedSince(ZonedDateTime ifModifiedSince);
    S ifNoneMatch(String... ifNoneMatches);
    
    ResponseSpec exchange();
}

RequestBodyUriSpec

public interface RequestBodyUriSpec extends RequestBodySpec, RequestHeadersUriSpec<RequestBodySpec> {
    
}

public interface RequestBodySpec extends RequestHeadersSpec<RequestBodySpec> {
    RequestHeadersSpec<?> contentLength(long contentLength);
    RequestHeadersSpec<?> contentType(MediaType contentType);
    
    RequestHeadersSpec<?> body(BodyInserter<?, ? super ClientHttpRequest> inserter);
    <T> RequestHeadersSpec<?> body(Mono<T> body, Class<T> elementClass);
    <T> RequestHeadersSpec<?> body(Mono<T> body, ParameterizedTypeReference<T> elementTypeRef);
    <T> RequestHeadersSpec<?> body(Flux<T> body, Class<T> elementClass);
    <T> RequestHeadersSpec<?> body(Flux<T> body, ParameterizedTypeReference<T> elementTypeRef);
    RequestHeadersSpec<?> body(Object body);
    
    RequestHeadersSpec<?> bodyValue(Object body);
}

Response Specification and Assertions

ResponseSpec

public interface ResponseSpec {
    
    StatusAssertions expectStatus();
    HeaderAssertions expectHeader();
    JsonPathAssertions expectJsonPath(String expression, Object... args);
    XPathAssertions expectXPath(String expression, Object... args, Map<String, String> namespaces);
    BodySpec<String, ?> expectBody(String content);
    <T> BodySpec<T, ?> expectBody(Class<T> bodyType);
    <T> BodySpec<T, ?> expectBody(ParameterizedTypeReference<T> bodyType);
    <T> ListBodySpec<T> expectBodyList(Class<T> elementType);
    <T> ListBodySpec<T> expectBodyList(ParameterizedTypeReference<T> elementType);
    
    ResponseSpec expectBody();
    
    <T> FluxExchangeResult<T> returnResult(Class<T> elementType);
    <T> FluxExchangeResult<T> returnResult(ParameterizedTypeReference<T> elementTypeRef);
}

StatusAssertions

public interface StatusAssertions {
    WebTestClient.ResponseSpec isOk();
    WebTestClient.ResponseSpec isCreated();
    WebTestClient.ResponseSpec isAccepted();
    WebTestClient.ResponseSpec isNoContent();
    WebTestClient.ResponseSpec isBadRequest();
    WebTestClient.ResponseSpec isUnauthorized();
    WebTestClient.ResponseSpec isForbidden();
    WebTestClient.ResponseSpec isNotFound();
    WebTestClient.ResponseSpec isEqualTo(HttpStatus status);
    WebTestClient.ResponseSpec isEqualTo(int status);
    WebTestClient.ResponseSpec is1xxInformational();
    WebTestClient.ResponseSpec is2xxSuccessful();
    WebTestClient.ResponseSpec is3xxRedirection();
    WebTestClient.ResponseSpec is4xxClientError();
    WebTestClient.ResponseSpec is5xxServerError();
}

Testing Examples

Controller Testing

@ExtendWith(SpringExtension.class)
@WebFluxTest(UserController.class)
class UserControllerTest {
    
    @Autowired
    private WebTestClient webTestClient;
    
    @MockBean
    private UserService userService;
    
    @Test
    void shouldGetUser() {
        // Given
        User user = new User("1", "John Doe", "john@example.com");
        when(userService.findById("1")).thenReturn(Mono.just(user));
        
        // When & Then
        webTestClient.get()
            .uri("/api/users/{id}", "1")
            .accept(MediaType.APPLICATION_JSON)
            .exchange()
            .expectStatus().isOk()
            .expectHeader().contentType(MediaType.APPLICATION_JSON)
            .expectBody(User.class)
            .value(u -> {
                assertThat(u.getId()).isEqualTo("1");
                assertThat(u.getName()).isEqualTo("John Doe");
                assertThat(u.getEmail()).isEqualTo("john@example.com");
            });
    }
    
    @Test
    void shouldCreateUser() {
        // Given
        User newUser = new User(null, "Jane Smith", "jane@example.com");
        User savedUser = new User("2", "Jane Smith", "jane@example.com");
        when(userService.save(any(User.class))).thenReturn(Mono.just(savedUser));
        
        // When & Then
        webTestClient.post()
            .uri("/api/users")
            .contentType(MediaType.APPLICATION_JSON)
            .body(Mono.just(newUser), User.class)
            .exchange()
            .expectStatus().isCreated()
            .expectBody(User.class)
            .value(u -> assertThat(u.getId()).isEqualTo("2"));
    }
    
    @Test
    void shouldGetAllUsers() {
        // Given
        List<User> users = Arrays.asList(
            new User("1", "John Doe", "john@example.com"),
            new User("2", "Jane Smith", "jane@example.com")
        );
        when(userService.findAll()).thenReturn(Flux.fromIterable(users));
        
        // When & Then
        webTestClient.get()
            .uri("/api/users")
            .accept(MediaType.APPLICATION_JSON)
            .exchange()
            .expectStatus().isOk()
            .expectBodyList(User.class)
            .hasSize(2)
            .contains(users.get(0), users.get(1));
    }
}

Router Function Testing

@ExtendWith(SpringExtension.class)
@WebFluxTest
class UserRouterTest {
    
    @Autowired
    private WebTestClient webTestClient;
    
    @MockBean
    private UserHandler userHandler;
    
    @TestConfiguration
    static class TestConfig {
        @Bean
        @Primary
        RouterFunction<ServerResponse> testRoutes(UserHandler handler) {
            return RouterFunctions.route()
                .GET("/api/users/{id}", handler::getUser)
                .GET("/api/users", handler::listUsers)
                .POST("/api/users", handler::createUser)
                .build();
        }
    }
    
    @Test
    void shouldRouteToGetUser() {
        // Given
        ServerRequest request = MockServerRequest.builder()
            .method(HttpMethod.GET)
            .uri(URI.create("/api/users/1"))
            .pathVariable("id", "1")
            .build();
        
        User user = new User("1", "John Doe", "john@example.com");
        when(userHandler.getUser(any(ServerRequest.class)))
            .thenReturn(ServerResponse.ok().body(Mono.just(user), User.class));
        
        // When & Then
        webTestClient.get()
            .uri("/api/users/{id}", "1")
            .exchange()
            .expectStatus().isOk();
    }
}

Integration Testing

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class UserIntegrationTest {
    
    @Autowired
    private WebTestClient webTestClient;
    
    @Autowired
    private UserRepository userRepository;
    
    @BeforeEach
    void setUp() {
        userRepository.deleteAll().block();
    }
    
    @Test
    void shouldPerformFullUserLifecycle() {
        // Create user
        User newUser = new User(null, "Integration Test", "integration@example.com");
        
        User createdUser = webTestClient.post()
            .uri("/api/users")
            .contentType(MediaType.APPLICATION_JSON)
            .body(Mono.just(newUser), User.class)
            .exchange()
            .expectStatus().isCreated()
            .expectBody(User.class)
            .returnResult()
            .getResponseBody();
        
        assertThat(createdUser.getId()).isNotNull();
        
        // Get user
        webTestClient.get()
            .uri("/api/users/{id}", createdUser.getId())
            .exchange()
            .expectStatus().isOk()
            .expectBody(User.class)
            .value(u -> {
                assertThat(u.getId()).isEqualTo(createdUser.getId());
                assertThat(u.getName()).isEqualTo("Integration Test");
            });
        
        // Update user
        User updatedUser = new User(createdUser.getId(), "Updated Name", "updated@example.com");
        
        webTestClient.put()
            .uri("/api/users/{id}", createdUser.getId())
            .contentType(MediaType.APPLICATION_JSON)
            .body(Mono.just(updatedUser), User.class)
            .exchange()
            .expectStatus().isOk()
            .expectBody(User.class)
            .value(u -> assertThat(u.getName()).isEqualTo("Updated Name"));
        
        // Delete user
        webTestClient.delete()
            .uri("/api/users/{id}", createdUser.getId())
            .exchange()
            .expectStatus().isNoContent();
        
        // Verify deletion
        webTestClient.get()
            .uri("/api/users/{id}", createdUser.getId())
            .exchange()
            .expectStatus().isNotFound();
    }
}

Custom WebTestClient Configuration

@TestConfiguration
public class WebTestClientConfig {
    
    @Bean
    @Primary
    public WebTestClient customWebTestClient() {
        return WebTestClient.bindToServer()
            .baseUrl("http://localhost:8080")
            .responseTimeout(Duration.ofSeconds(30))
            .filter(logRequest())
            .filter(logResponse())
            .defaultHeader("Accept", MediaType.APPLICATION_JSON_VALUE)
            .defaultCookie("SESSION", "test-session")
            .build();
    }
    
    private ExchangeFilterFunction logRequest() {
        return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
            System.out.println("Request: " + clientRequest.method() + " " + clientRequest.url());
            clientRequest.headers().forEach((name, values) ->
                values.forEach(value -> System.out.println(name + ": " + value))
            );
            return Mono.just(clientRequest);
        });
    }
    
    private ExchangeFilterFunction logResponse() {
        return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
            System.out.println("Response: " + clientResponse.statusCode());
            return Mono.just(clientResponse);
        });
    }
}

Testing Reactive Streams

@Test
void shouldTestReactiveStream() {
    Flux<String> source = Flux.just("foo", "bar", "baz");
    
    StepVerifier.create(source)
        .expectNext("foo")
        .expectNext("bar")
        .expectNext("baz")
        .verifyComplete();
}

@Test
void shouldTestErrorHandling() {
    Flux<String> source = Flux.just("foo", "bar")
        .concatWith(Flux.error(new RuntimeException("boom")));
    
    StepVerifier.create(source)
        .expectNext("foo")
        .expectNext("bar")
        .expectError(RuntimeException.class)
        .verify();
}

@Test
void shouldTestWithVirtualTime() {
    StepVerifier.withVirtualTime(() -> 
            Flux.interval(Duration.ofHours(4)).take(2))
        .expectSubscription()
        .expectNoEvent(Duration.ofHours(4))
        .expectNext(0L)
        .thenAwait(Duration.ofHours(4))
        .expectNext(1L)
        .verifyComplete();
}

Test Slices

// Test only WebFlux layer
@WebFluxTest(UserController.class)
class WebFluxLayerTest {
    // Only WebFlux components are loaded
}

// Test with custom auto-configuration
@WebFluxTest(
    controllers = UserController.class,
    excludeAutoConfiguration = SecurityAutoConfiguration.class
)
class CustomWebFluxTest {
    // WebFlux with custom configuration
}

// Test specific components
@TestConfiguration
static class TestConfig {
    @Bean
    @Primary
    public UserService mockUserService() {
        return Mockito.mock(UserService.class);
    }
}

Install with Tessl CLI

npx tessl i tessl/maven-org-springframework-boot--spring-boot-starter-webflux

docs

annotation-controllers.md

configuration.md

error-handling.md

functional-routing.md

index.md

server-configuration.md

testing.md

webclient.md

tile.json