001/* 002 * Copyright 2002-2020 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.test.web.reactive.server; 018 019import java.net.URI; 020import java.nio.charset.Charset; 021import java.time.Duration; 022import java.time.ZonedDateTime; 023import java.util.List; 024import java.util.Map; 025import java.util.function.Consumer; 026import java.util.function.Function; 027 028import org.hamcrest.Matcher; 029import org.reactivestreams.Publisher; 030 031import org.springframework.context.ApplicationContext; 032import org.springframework.core.ParameterizedTypeReference; 033import org.springframework.core.ReactiveAdapterRegistry; 034import org.springframework.format.FormatterRegistry; 035import org.springframework.http.HttpHeaders; 036import org.springframework.http.HttpMethod; 037import org.springframework.http.MediaType; 038import org.springframework.http.client.reactive.ClientHttpConnector; 039import org.springframework.http.client.reactive.ClientHttpRequest; 040import org.springframework.http.codec.ClientCodecConfigurer; 041import org.springframework.http.codec.ServerCodecConfigurer; 042import org.springframework.lang.Nullable; 043import org.springframework.util.MultiValueMap; 044import org.springframework.validation.Validator; 045import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder; 046import org.springframework.web.reactive.config.CorsRegistry; 047import org.springframework.web.reactive.config.PathMatchConfigurer; 048import org.springframework.web.reactive.config.ViewResolverRegistry; 049import org.springframework.web.reactive.config.WebFluxConfigurer; 050import org.springframework.web.reactive.function.BodyInserter; 051import org.springframework.web.reactive.function.BodyInserters; 052import org.springframework.web.reactive.function.client.ExchangeFilterFunction; 053import org.springframework.web.reactive.function.client.ExchangeStrategies; 054import org.springframework.web.reactive.function.client.WebClient; 055import org.springframework.web.reactive.function.server.HandlerStrategies; 056import org.springframework.web.reactive.function.server.RouterFunction; 057import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer; 058import org.springframework.web.server.WebFilter; 059import org.springframework.web.server.WebHandler; 060import org.springframework.web.server.session.WebSessionManager; 061import org.springframework.web.util.UriBuilder; 062import org.springframework.web.util.UriBuilderFactory; 063 064/** 065 * Client for testing web servers that uses {@link WebClient} internally to 066 * perform requests while also providing a fluent API to verify responses. 067 * This client can connect to any server over HTTP, or to a WebFlux application 068 * via mock request and response objects. 069 * 070 * <p>Use one of the bindToXxx methods to create an instance. For example: 071 * <ul> 072 * <li>{@link #bindToController(Object...)} 073 * <li>{@link #bindToRouterFunction(RouterFunction)} 074 * <li>{@link #bindToApplicationContext(ApplicationContext)} 075 * <li>{@link #bindToServer()} 076 * <li>... 077 * </ul> 078 * 079 * <p><strong>Warning</strong>: {@code WebTestClient} is not usable yet in 080 * Kotlin due to a <a href="https://youtrack.jetbrains.com/issue/KT-5464">type inference issue</a> 081 * which is expected to be fixed as of Kotlin 1.3. You can watch 082 * <a href="https://github.com/spring-projects/spring-framework/issues/20606">gh-20606</a> 083 * for up-to-date information. Meanwhile, the proposed alternative is to use 084 * directly {@link WebClient} with its Reactor and Spring Kotlin extensions to 085 * perform integration tests on an embedded WebFlux server. 086 * 087 * @author Rossen Stoyanchev 088 * @author Brian Clozel 089 * @since 5.0 090 * @see StatusAssertions 091 * @see HeaderAssertions 092 * @see JsonPathAssertions 093 */ 094public interface WebTestClient { 095 096 /** 097 * The name of a request header used to assign a unique id to every request 098 * performed through the {@code WebTestClient}. This can be useful for 099 * storing contextual information at all phases of request processing (e.g. 100 * from a server-side component) under that id and later to look up 101 * that information once an {@link ExchangeResult} is available. 102 */ 103 String WEBTESTCLIENT_REQUEST_ID = "WebTestClient-Request-Id"; 104 105 106 /** 107 * Prepare an HTTP GET request. 108 * @return a spec for specifying the target URL 109 */ 110 RequestHeadersUriSpec<?> get(); 111 112 /** 113 * Prepare an HTTP HEAD request. 114 * @return a spec for specifying the target URL 115 */ 116 RequestHeadersUriSpec<?> head(); 117 118 /** 119 * Prepare an HTTP POST request. 120 * @return a spec for specifying the target URL 121 */ 122 RequestBodyUriSpec post(); 123 124 /** 125 * Prepare an HTTP PUT request. 126 * @return a spec for specifying the target URL 127 */ 128 RequestBodyUriSpec put(); 129 130 /** 131 * Prepare an HTTP PATCH request. 132 * @return a spec for specifying the target URL 133 */ 134 RequestBodyUriSpec patch(); 135 136 /** 137 * Prepare an HTTP DELETE request. 138 * @return a spec for specifying the target URL 139 */ 140 RequestHeadersUriSpec<?> delete(); 141 142 /** 143 * Prepare an HTTP OPTIONS request. 144 * @return a spec for specifying the target URL 145 */ 146 RequestHeadersUriSpec<?> options(); 147 148 /** 149 * Prepare a request for the specified {@code HttpMethod}. 150 * @return a spec for specifying the target URL 151 */ 152 RequestBodyUriSpec method(HttpMethod method); 153 154 155 /** 156 * Return a builder to mutate properties of this web test client. 157 */ 158 Builder mutate(); 159 160 /** 161 * Mutate the {@link WebTestClient}, apply the given configurer, and build 162 * a new instance. Essentially a shortcut for: 163 * <pre> 164 * mutate().apply(configurer).build(); 165 * </pre> 166 * @param configurer the configurer to apply 167 * @return the mutated test client 168 */ 169 WebTestClient mutateWith(WebTestClientConfigurer configurer); 170 171 172 // Static factory methods 173 174 /** 175 * Use this server setup to test one `@Controller` at a time. 176 * This option loads the default configuration of 177 * {@link org.springframework.web.reactive.config.EnableWebFlux @EnableWebFlux}. 178 * There are builder methods to customize the Java config. The resulting 179 * WebFlux application will be tested without an HTTP server using a mock 180 * request and response. 181 * @param controllers one or more controller instances to tests 182 * (specified {@code Class} will be turned into instance) 183 * @return chained API to customize server and client config; use 184 * {@link MockServerSpec#configureClient()} to transition to client config 185 */ 186 static ControllerSpec bindToController(Object... controllers) { 187 return new DefaultControllerSpec(controllers); 188 } 189 190 /** 191 * Use this option to set up a server from a {@link RouterFunction}. 192 * Internally the provided configuration is passed to 193 * {@code RouterFunctions#toWebHandler}. The resulting WebFlux application 194 * will be tested without an HTTP server using a mock request and response. 195 * @param routerFunction the RouterFunction to test 196 * @return chained API to customize server and client config; use 197 * {@link MockServerSpec#configureClient()} to transition to client config 198 */ 199 static RouterFunctionSpec bindToRouterFunction(RouterFunction<?> routerFunction) { 200 return new DefaultRouterFunctionSpec(routerFunction); 201 } 202 203 /** 204 * Use this option to setup a server from the Spring configuration of your 205 * application, or some subset of it. Internally the provided configuration 206 * is passed to {@code WebHttpHandlerBuilder} to set up the request 207 * processing chain. The resulting WebFlux application will be tested 208 * without an HTTP server using a mock request and response. 209 * <p>Consider using the TestContext framework and 210 * {@link org.springframework.test.context.ContextConfiguration @ContextConfiguration} 211 * in order to efficiently load and inject the Spring configuration into the 212 * test class. 213 * @param applicationContext the Spring context 214 * @return chained API to customize server and client config; use 215 * {@link MockServerSpec#configureClient()} to transition to client config 216 */ 217 static MockServerSpec<?> bindToApplicationContext(ApplicationContext applicationContext) { 218 return new ApplicationContextSpec(applicationContext); 219 } 220 221 /** 222 * Integration testing with a "mock" server targeting the given WebHandler. 223 * @param webHandler the handler to test 224 * @return chained API to customize server and client config; use 225 * {@link MockServerSpec#configureClient()} to transition to client config 226 */ 227 static MockServerSpec<?> bindToWebHandler(WebHandler webHandler) { 228 return new DefaultMockServerSpec(webHandler); 229 } 230 231 /** 232 * This server setup option allows you to connect to a running server via 233 * Reactor Netty. 234 * <p><pre class="code"> 235 * WebTestClient client = WebTestClient.bindToServer() 236 * .baseUrl("http://localhost:8080") 237 * .build(); 238 * </pre> 239 * @return chained API to customize client config 240 */ 241 static Builder bindToServer() { 242 return new DefaultWebTestClientBuilder(); 243 } 244 245 /** 246 * A variant of {@link #bindToServer()} with a pre-configured connector. 247 * <p><pre class="code"> 248 * WebTestClient client = WebTestClient.bindToServer() 249 * .baseUrl("http://localhost:8080") 250 * .build(); 251 * </pre> 252 * @return chained API to customize client config 253 * @since 5.0.2 254 */ 255 static Builder bindToServer(ClientHttpConnector connector) { 256 return new DefaultWebTestClientBuilder(connector); 257 } 258 259 260 /** 261 * Base specification for setting up tests without a server. 262 * 263 * @param <B> a self reference to the builder type 264 */ 265 interface MockServerSpec<B extends MockServerSpec<B>> { 266 267 /** 268 * Register {@link WebFilter} instances to add to the mock server. 269 * @param filter one or more filters 270 */ 271 <T extends B> T webFilter(WebFilter... filter); 272 273 /** 274 * Provide a session manager instance for the mock server. 275 * <p>By default an instance of 276 * {@link org.springframework.web.server.session.DefaultWebSessionManager 277 * DefaultWebSessionManager} is used. 278 * @param sessionManager the session manager to use 279 */ 280 <T extends B> T webSessionManager(WebSessionManager sessionManager); 281 282 /** 283 * Shortcut for pre-packaged customizations to the mock server setup. 284 * @param configurer the configurer to apply 285 */ 286 <T extends B> T apply(MockServerConfigurer configurer); 287 288 /** 289 * Proceed to configure and build the test client. 290 */ 291 Builder configureClient(); 292 293 /** 294 * Shortcut to build the test client. 295 */ 296 WebTestClient build(); 297 } 298 299 300 /** 301 * Specification for customizing controller configuration equivalent to, and 302 * internally delegating to, a {@link WebFluxConfigurer}. 303 */ 304 interface ControllerSpec extends MockServerSpec<ControllerSpec> { 305 306 /** 307 * Register one or more {@link org.springframework.web.bind.annotation.ControllerAdvice} 308 * instances to be used in tests (specified {@code Class} will be turned into instance). 309 */ 310 ControllerSpec controllerAdvice(Object... controllerAdvice); 311 312 /** 313 * Customize content type resolution. 314 * @see WebFluxConfigurer#configureContentTypeResolver 315 */ 316 ControllerSpec contentTypeResolver(Consumer<RequestedContentTypeResolverBuilder> consumer); 317 318 /** 319 * Configure CORS support. 320 * @see WebFluxConfigurer#addCorsMappings 321 */ 322 ControllerSpec corsMappings(Consumer<CorsRegistry> consumer); 323 324 /** 325 * Configure path matching options. 326 * @see WebFluxConfigurer#configurePathMatching 327 */ 328 ControllerSpec pathMatching(Consumer<PathMatchConfigurer> consumer); 329 330 /** 331 * Configure resolvers for custom controller method arguments. 332 * @see WebFluxConfigurer#configureHttpMessageCodecs 333 */ 334 ControllerSpec argumentResolvers(Consumer<ArgumentResolverConfigurer> configurer); 335 336 /** 337 * Configure custom HTTP message readers and writers or override built-in ones. 338 * @see WebFluxConfigurer#configureHttpMessageCodecs 339 */ 340 ControllerSpec httpMessageCodecs(Consumer<ServerCodecConfigurer> configurer); 341 342 /** 343 * Register formatters and converters to use for type conversion. 344 * @see WebFluxConfigurer#addFormatters 345 */ 346 ControllerSpec formatters(Consumer<FormatterRegistry> consumer); 347 348 /** 349 * Configure a global Validator. 350 * @see WebFluxConfigurer#getValidator() 351 */ 352 ControllerSpec validator(Validator validator); 353 354 /** 355 * Configure view resolution. 356 * @see WebFluxConfigurer#configureViewResolvers 357 */ 358 ControllerSpec viewResolvers(Consumer<ViewResolverRegistry> consumer); 359 } 360 361 362 /** 363 * Specification for customizing router function configuration. 364 */ 365 interface RouterFunctionSpec extends MockServerSpec<RouterFunctionSpec> { 366 367 /** 368 * Configure handler strategies. 369 */ 370 RouterFunctionSpec handlerStrategies(HandlerStrategies handlerStrategies); 371 } 372 373 374 /** 375 * Steps for customizing the {@link WebClient} used to test with, 376 * internally delegating to a 377 * {@link org.springframework.web.reactive.function.client.WebClient.Builder 378 * WebClient.Builder}. 379 */ 380 interface Builder { 381 382 /** 383 * Configure a base URI as described in 384 * {@link org.springframework.web.reactive.function.client.WebClient#create(String) 385 * WebClient.create(String)}. 386 */ 387 Builder baseUrl(String baseUrl); 388 389 /** 390 * Provide a pre-configured {@link UriBuilderFactory} instance as an 391 * alternative to and effectively overriding {@link #baseUrl(String)}. 392 */ 393 Builder uriBuilderFactory(UriBuilderFactory uriBuilderFactory); 394 395 /** 396 * Add the given header to all requests that haven't added it. 397 * @param headerName the header name 398 * @param headerValues the header values 399 */ 400 Builder defaultHeader(String headerName, String... headerValues); 401 402 /** 403 * Manipulate the default headers with the given consumer. The 404 * headers provided to the consumer are "live", so that the consumer can be used to 405 * {@linkplain HttpHeaders#set(String, String) overwrite} existing header values, 406 * {@linkplain HttpHeaders#remove(Object) remove} values, or use any of the other 407 * {@link HttpHeaders} methods. 408 * @param headersConsumer a function that consumes the {@code HttpHeaders} 409 * @return this builder 410 */ 411 Builder defaultHeaders(Consumer<HttpHeaders> headersConsumer); 412 413 /** 414 * Add the given header to all requests that haven't added it. 415 * @param cookieName the cookie name 416 * @param cookieValues the cookie values 417 */ 418 Builder defaultCookie(String cookieName, String... cookieValues); 419 420 /** 421 * Manipulate the default cookies with the given consumer. The 422 * map provided to the consumer is "live", so that the consumer can be used to 423 * {@linkplain MultiValueMap#set(Object, Object) overwrite} existing header values, 424 * {@linkplain MultiValueMap#remove(Object) remove} values, or use any of the other 425 * {@link MultiValueMap} methods. 426 * @param cookiesConsumer a function that consumes the cookies map 427 * @return this builder 428 */ 429 Builder defaultCookies(Consumer<MultiValueMap<String, String>> cookiesConsumer); 430 431 /** 432 * Add the given filter to the filter chain. 433 * @param filter the filter to be added to the chain 434 */ 435 Builder filter(ExchangeFilterFunction filter); 436 437 /** 438 * Manipulate the filters with the given consumer. The 439 * list provided to the consumer is "live", so that the consumer can be used to remove 440 * filters, change ordering, etc. 441 * @param filtersConsumer a function that consumes the filter list 442 * @return this builder 443 */ 444 Builder filters(Consumer<List<ExchangeFilterFunction>> filtersConsumer); 445 446 /** 447 * Configure the codecs for the {@code WebClient} in the 448 * {@link #exchangeStrategies(ExchangeStrategies) underlying} 449 * {@code ExchangeStrategies}. 450 * @param configurer the configurer to apply 451 * @since 5.1.13 452 */ 453 Builder codecs(Consumer<ClientCodecConfigurer> configurer); 454 455 /** 456 * Configure the {@link ExchangeStrategies} to use. 457 * <p>For most cases, prefer using {@link #codecs(Consumer)} which allows 458 * customizing the codecs in the {@code ExchangeStrategies} rather than 459 * replace them. That ensures multiple parties can contribute to codecs 460 * configuration. 461 * <p>By default this is set to {@link ExchangeStrategies#withDefaults()}. 462 * @param strategies the strategies to use 463 */ 464 Builder exchangeStrategies(ExchangeStrategies strategies); 465 466 /** 467 * Customize the strategies configured via 468 * {@link #exchangeStrategies(ExchangeStrategies)}. This method is 469 * designed for use in scenarios where multiple parties wish to update 470 * the {@code ExchangeStrategies}. 471 * @deprecated as of 5.1.13 in favor of {@link #codecs(Consumer)} 472 */ 473 @Deprecated 474 Builder exchangeStrategies(Consumer<ExchangeStrategies.Builder> configurer); 475 476 /** 477 * Max amount of time to wait for responses. 478 * <p>By default 5 seconds. 479 * @param timeout the response timeout value 480 */ 481 Builder responseTimeout(Duration timeout); 482 483 /** 484 * Apply the given configurer to this builder instance. 485 * <p>This can be useful for applying pre-packaged customizations. 486 * @param configurer the configurer to apply 487 */ 488 Builder apply(WebTestClientConfigurer configurer); 489 490 /** 491 * Build the {@link WebTestClient} instance. 492 */ 493 WebTestClient build(); 494 } 495 496 497 /** 498 * Specification for providing the URI of a request. 499 * 500 * @param <S> a self reference to the spec type 501 */ 502 interface UriSpec<S extends RequestHeadersSpec<?>> { 503 504 /** 505 * Specify the URI using an absolute, fully constructed {@link URI}. 506 * @return spec to add headers or perform the exchange 507 */ 508 S uri(URI uri); 509 510 /** 511 * Specify the URI for the request using a URI template and URI variables. 512 * If a {@link UriBuilderFactory} was configured for the client (e.g. 513 * with a base URI) it will be used to expand the URI template. 514 * @return spec to add headers or perform the exchange 515 */ 516 S uri(String uri, Object... uriVariables); 517 518 /** 519 * Specify the URI for the request using a URI template and URI variables. 520 * If a {@link UriBuilderFactory} was configured for the client (e.g. 521 * with a base URI) it will be used to expand the URI template. 522 * @return spec to add headers or perform the exchange 523 */ 524 S uri(String uri, Map<String, ?> uriVariables); 525 526 /** 527 * Build the URI for the request with a {@link UriBuilder} obtained 528 * through the {@link UriBuilderFactory} configured for this client. 529 * @return spec to add headers or perform the exchange 530 */ 531 S uri(Function<UriBuilder, URI> uriFunction); 532 } 533 534 535 /** 536 * Specification for adding request headers and performing an exchange. 537 * 538 * @param <S> a self reference to the spec type 539 */ 540 interface RequestHeadersSpec<S extends RequestHeadersSpec<S>> { 541 542 /** 543 * Set the list of acceptable {@linkplain MediaType media types}, as 544 * specified by the {@code Accept} header. 545 * @param acceptableMediaTypes the acceptable media types 546 * @return the same instance 547 */ 548 S accept(MediaType... acceptableMediaTypes); 549 550 /** 551 * Set the list of acceptable {@linkplain Charset charsets}, as specified 552 * by the {@code Accept-Charset} header. 553 * @param acceptableCharsets the acceptable charsets 554 * @return the same instance 555 */ 556 S acceptCharset(Charset... acceptableCharsets); 557 558 /** 559 * Add a cookie with the given name and value. 560 * @param name the cookie name 561 * @param value the cookie value 562 * @return the same instance 563 */ 564 S cookie(String name, String value); 565 566 /** 567 * Manipulate this request's cookies with the given consumer. The 568 * map provided to the consumer is "live", so that the consumer can be used to 569 * {@linkplain MultiValueMap#set(Object, Object) overwrite} existing header values, 570 * {@linkplain MultiValueMap#remove(Object) remove} values, or use any of the other 571 * {@link MultiValueMap} methods. 572 * @param cookiesConsumer a function that consumes the cookies map 573 * @return this builder 574 */ 575 S cookies(Consumer<MultiValueMap<String, String>> cookiesConsumer); 576 577 /** 578 * Set the value of the {@code If-Modified-Since} header. 579 * <p>The date should be specified as the number of milliseconds since 580 * January 1, 1970 GMT. 581 * @param ifModifiedSince the new value of the header 582 * @return the same instance 583 */ 584 S ifModifiedSince(ZonedDateTime ifModifiedSince); 585 586 /** 587 * Set the values of the {@code If-None-Match} header. 588 * @param ifNoneMatches the new value of the header 589 * @return the same instance 590 */ 591 S ifNoneMatch(String... ifNoneMatches); 592 593 /** 594 * Add the given, single header value under the given name. 595 * @param headerName the header name 596 * @param headerValues the header value(s) 597 * @return the same instance 598 */ 599 S header(String headerName, String... headerValues); 600 601 /** 602 * Manipulate the request's headers with the given consumer. The 603 * headers provided to the consumer are "live", so that the consumer can be used to 604 * {@linkplain HttpHeaders#set(String, String) overwrite} existing header values, 605 * {@linkplain HttpHeaders#remove(Object) remove} values, or use any of the other 606 * {@link HttpHeaders} methods. 607 * @param headersConsumer a function that consumes the {@code HttpHeaders} 608 * @return this builder 609 */ 610 S headers(Consumer<HttpHeaders> headersConsumer); 611 612 /** 613 * Set the attribute with the given name to the given value. 614 * @param name the name of the attribute to add 615 * @param value the value of the attribute to add 616 * @return this builder 617 */ 618 S attribute(String name, Object value); 619 620 /** 621 * Manipulate the request attributes with the given consumer. The attributes provided to 622 * the consumer are "live", so that the consumer can be used to inspect attributes, 623 * remove attributes, or use any of the other map-provided methods. 624 * @param attributesConsumer a function that consumes the attributes 625 * @return this builder 626 */ 627 S attributes(Consumer<Map<String, Object>> attributesConsumer); 628 629 /** 630 * Perform the exchange without a request body. 631 * @return spec for decoding the response 632 */ 633 ResponseSpec exchange(); 634 } 635 636 637 /** 638 * Specification for providing body of a request. 639 */ 640 interface RequestBodySpec extends RequestHeadersSpec<RequestBodySpec> { 641 /** 642 * Set the length of the body in bytes, as specified by the 643 * {@code Content-Length} header. 644 * @param contentLength the content length 645 * @return the same instance 646 * @see HttpHeaders#setContentLength(long) 647 */ 648 RequestBodySpec contentLength(long contentLength); 649 650 /** 651 * Set the {@linkplain MediaType media type} of the body, as specified 652 * by the {@code Content-Type} header. 653 * @param contentType the content type 654 * @return the same instance 655 * @see HttpHeaders#setContentType(MediaType) 656 */ 657 RequestBodySpec contentType(MediaType contentType); 658 659 /** 660 * Set the body to the given {@code Object} value. This method invokes the 661 * {@link org.springframework.web.reactive.function.client.WebClient.RequestBodySpec#bodyValue(Object) 662 * bodyValue} method on the underlying {@code WebClient}. 663 * @param body the value to write to the request body 664 * @return spec for further declaration of the request 665 * @since 5.2 666 */ 667 RequestHeadersSpec<?> bodyValue(Object body); 668 669 /** 670 * Set the body from the given {@code Publisher}. Shortcut for 671 * {@link #body(BodyInserter)} with a 672 * {@linkplain BodyInserters#fromPublisher Publisher inserter}. 673 * @param publisher the request body data 674 * @param elementClass the class of elements contained in the publisher 675 * @param <T> the type of the elements contained in the publisher 676 * @param <S> the type of the {@code Publisher} 677 * @return spec for further declaration of the request 678 */ 679 <T, S extends Publisher<T>> RequestHeadersSpec<?> body(S publisher, Class<T> elementClass); 680 681 /** 682 * Variant of {@link #body(Publisher, Class)} that allows providing 683 * element type information with generics. 684 * @param publisher the request body data 685 * @param elementTypeRef the type reference of elements contained in the publisher 686 * @param <T> the type of the elements contained in the publisher 687 * @param <S> the type of the {@code Publisher} 688 * @return spec for further declaration of the request 689 * @since 5.2 690 */ 691 <T, S extends Publisher<T>> RequestHeadersSpec<?> body( 692 S publisher, ParameterizedTypeReference<T> elementTypeRef); 693 694 /** 695 * Set the body from the given producer. This method invokes the 696 * {@link org.springframework.web.reactive.function.client.WebClient.RequestBodySpec#body(Object, Class) 697 * body(Object, Class)} method on the underlying {@code WebClient}. 698 * @param producer the producer to write to the request. This must be a 699 * {@link Publisher} or another producer adaptable to a 700 * {@code Publisher} via {@link ReactiveAdapterRegistry} 701 * @param elementClass the class of elements contained in the producer 702 * @return spec for further declaration of the request 703 * @since 5.2 704 */ 705 RequestHeadersSpec<?> body(Object producer, Class<?> elementClass); 706 707 /** 708 * Set the body from the given producer. This method invokes the 709 * {@link org.springframework.web.reactive.function.client.WebClient.RequestBodySpec#body(Object, ParameterizedTypeReference) 710 * body(Object, ParameterizedTypeReference)} method on the underlying {@code WebClient}. 711 * @param producer the producer to write to the request. This must be a 712 * {@link Publisher} or another producer adaptable to a 713 * {@code Publisher} via {@link ReactiveAdapterRegistry} 714 * @param elementTypeRef the type reference of elements contained in the producer 715 * @return spec for further declaration of the request 716 * @since 5.2 717 */ 718 RequestHeadersSpec<?> body(Object producer, ParameterizedTypeReference<?> elementTypeRef); 719 720 /** 721 * Set the body of the request to the given {@code BodyInserter}. 722 * This method invokes the 723 * {@link org.springframework.web.reactive.function.client.WebClient.RequestBodySpec#body(BodyInserter) 724 * body(BodyInserter)} method on the underlying {@code WebClient}. 725 * @param inserter the body inserter to use 726 * @return spec for further declaration of the request 727 * @see org.springframework.web.reactive.function.BodyInserters 728 */ 729 RequestHeadersSpec<?> body(BodyInserter<?, ? super ClientHttpRequest> inserter); 730 731 /** 732 * Shortcut for {@link #body(BodyInserter)} with a 733 * {@linkplain BodyInserters#fromValue value inserter}. 734 * As of 5.2 this method delegates to {@link #bodyValue(Object)}. 735 * @deprecated as of Spring Framework 5.2 in favor of {@link #bodyValue(Object)} 736 */ 737 @Deprecated 738 RequestHeadersSpec<?> syncBody(Object body); 739 } 740 741 742 /** 743 * Specification for providing request headers and the URI of a request. 744 * 745 * @param <S> a self reference to the spec type 746 */ 747 interface RequestHeadersUriSpec<S extends RequestHeadersSpec<S>> extends UriSpec<S>, RequestHeadersSpec<S> { 748 } 749 750 /** 751 * Specification for providing the body and the URI of a request. 752 */ 753 interface RequestBodyUriSpec extends RequestBodySpec, RequestHeadersUriSpec<RequestBodySpec> { 754 } 755 756 757 /** 758 * Chained API for applying assertions to a response. 759 */ 760 interface ResponseSpec { 761 762 /** 763 * Assertions on the response status. 764 */ 765 StatusAssertions expectStatus(); 766 767 /** 768 * Assertions on the headers of the response. 769 */ 770 HeaderAssertions expectHeader(); 771 772 /** 773 * Consume and decode the response body to a single object of type 774 * {@code <B>} and then apply assertions. 775 * @param bodyType the expected body type 776 */ 777 <B> BodySpec<B, ?> expectBody(Class<B> bodyType); 778 779 /** 780 * Alternative to {@link #expectBody(Class)} that accepts information 781 * about a target type with generics. 782 */ 783 <B> BodySpec<B, ?> expectBody(ParameterizedTypeReference<B> bodyType); 784 785 /** 786 * Consume and decode the response body to {@code List<E>} and then apply 787 * List-specific assertions. 788 * @param elementType the expected List element type 789 */ 790 <E> ListBodySpec<E> expectBodyList(Class<E> elementType); 791 792 /** 793 * Alternative to {@link #expectBodyList(Class)} that accepts information 794 * about a target type with generics. 795 */ 796 <E> ListBodySpec<E> expectBodyList(ParameterizedTypeReference<E> elementType); 797 798 /** 799 * Consume and decode the response body to {@code byte[]} and then apply 800 * assertions on the raw content (e.g. isEmpty, JSONPath, etc.) 801 */ 802 BodyContentSpec expectBody(); 803 804 /** 805 * Exit the chained API and consume the response body externally. This 806 * is useful for testing infinite streams (e.g. SSE) where you need to 807 * to assert decoded objects as they come and then cancel at some point 808 * when test objectives are met. Consider using {@code StepVerifier} 809 * from {@literal "reactor-test"} to assert the {@code Flux<T>} stream 810 * of decoded objects. 811 * 812 * <p><strong>Note:</strong> Do not use this option for cases where there 813 * is no content (e.g. 204, 4xx) or you're not interested in the content. 814 * For such cases you can use {@code expectBody().isEmpty()} or 815 * {@code expectBody(Void.class)} which ensures that resources are 816 * released regardless of whether the response has content or not. 817 */ 818 <T> FluxExchangeResult<T> returnResult(Class<T> elementClass); 819 820 /** 821 * Alternative to {@link #returnResult(Class)} that accepts information 822 * about a target type with generics. 823 */ 824 <T> FluxExchangeResult<T> returnResult(ParameterizedTypeReference<T> elementTypeRef); 825 } 826 827 828 /** 829 * Spec for expectations on the response body decoded to a single Object. 830 * 831 * @param <S> a self reference to the spec type 832 * @param <B> the body type 833 */ 834 interface BodySpec<B, S extends BodySpec<B, S>> { 835 836 /** 837 * Assert the extracted body is equal to the given value. 838 */ 839 <T extends S> T isEqualTo(B expected); 840 841 /** 842 * Assert the extracted body with a {@link Matcher}. 843 * @since 5.1 844 */ 845 <T extends S> T value(Matcher<B> matcher); 846 847 /** 848 * Transform the extracted the body with a function, e.g. extracting a 849 * property, and assert the mapped value with a {@link Matcher}. 850 * @since 5.1 851 */ 852 <T extends S, R> T value(Function<B, R> bodyMapper, Matcher<R> matcher); 853 854 /** 855 * Assert the extracted body with a {@link Consumer}. 856 * @since 5.1 857 */ 858 <T extends S> T value(Consumer<B> consumer); 859 860 /** 861 * Assert the exchange result with the given {@link Consumer}. 862 */ 863 <T extends S> T consumeWith(Consumer<EntityExchangeResult<B>> consumer); 864 865 /** 866 * Exit the chained API and return an {@code ExchangeResult} with the 867 * decoded response content. 868 */ 869 EntityExchangeResult<B> returnResult(); 870 } 871 872 873 /** 874 * Spec for expectations on the response body decoded to a List. 875 * 876 * @param <E> the body list element type 877 */ 878 interface ListBodySpec<E> extends BodySpec<List<E>, ListBodySpec<E>> { 879 880 /** 881 * Assert the extracted list of values is of the given size. 882 * @param size the expected size 883 */ 884 ListBodySpec<E> hasSize(int size); 885 886 /** 887 * Assert the extracted list of values contains the given elements. 888 * @param elements the elements to check 889 */ 890 @SuppressWarnings("unchecked") 891 ListBodySpec<E> contains(E... elements); 892 893 /** 894 * Assert the extracted list of values doesn't contain the given elements. 895 * @param elements the elements to check 896 */ 897 @SuppressWarnings("unchecked") 898 ListBodySpec<E> doesNotContain(E... elements); 899 } 900 901 902 /** 903 * Spec for expectations on the response body content. 904 */ 905 interface BodyContentSpec { 906 907 /** 908 * Assert the response body is empty and return the exchange result. 909 */ 910 EntityExchangeResult<Void> isEmpty(); 911 912 /** 913 * Parse the expected and actual response content as JSON and perform a 914 * "lenient" comparison verifying the same attribute-value pairs. 915 * <p>Use of this option requires the 916 * <a href="https://jsonassert.skyscreamer.org/">JSONassert</a> library 917 * on to be on the classpath. 918 * @param expectedJson the expected JSON content. 919 */ 920 BodyContentSpec json(String expectedJson); 921 922 /** 923 * Parse expected and actual response content as XML and assert that 924 * the two are "similar", i.e. they contain the same elements and 925 * attributes regardless of order. 926 * <p>Use of this method requires the 927 * <a href="https://github.com/xmlunit/xmlunit">XMLUnit</a> library on 928 * the classpath. 929 * @param expectedXml the expected JSON content. 930 * @since 5.1 931 * @see org.springframework.test.util.XmlExpectationsHelper#assertXmlEqual(String, String) 932 */ 933 BodyContentSpec xml(String expectedXml); 934 935 /** 936 * Access to response body assertions using a 937 * <a href="https://github.com/jayway/JsonPath">JsonPath</a> expression 938 * to inspect a specific subset of the body. 939 * <p>The JSON path expression can be a parameterized string using 940 * formatting specifiers as defined in {@link String#format}. 941 * @param expression the JsonPath expression 942 * @param args arguments to parameterize the expression 943 */ 944 JsonPathAssertions jsonPath(String expression, Object... args); 945 946 /** 947 * Access to response body assertions using an XPath expression to 948 * inspect a specific subset of the body. 949 * <p>The XPath expression can be a parameterized string using 950 * formatting specifiers as defined in {@link String#format}. 951 * @param expression the XPath expression 952 * @param args arguments to parameterize the expression 953 * @since 5.1 954 * @see #xpath(String, Map, Object...) 955 */ 956 default XpathAssertions xpath(String expression, Object... args) { 957 return xpath(expression, null, args); 958 } 959 960 /** 961 * Access to response body assertions with specific namespaces using an 962 * XPath expression to inspect a specific subset of the body. 963 * <p>The XPath expression can be a parameterized string using 964 * formatting specifiers as defined in {@link String#format}. 965 * @param expression the XPath expression 966 * @param namespaces the namespaces to use 967 * @param args arguments to parameterize the expression 968 * @since 5.1 969 */ 970 XpathAssertions xpath(String expression, @Nullable Map<String, String> namespaces, Object... args); 971 972 /** 973 * Assert the response body content with the given {@link Consumer}. 974 * @param consumer the consumer for the response body; the input 975 * {@code byte[]} may be {@code null} if there was no response body. 976 */ 977 BodyContentSpec consumeWith(Consumer<EntityExchangeResult<byte[]>> consumer); 978 979 /** 980 * Exit the chained API and return an {@code ExchangeResult} with the 981 * raw response content. 982 */ 983 EntityExchangeResult<byte[]> returnResult(); 984 } 985 986}