From c6b87b3ef4a9296131a9c6c478b4ce480ea4c5bd Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Mon, 31 Aug 2020 21:14:45 +0100 Subject: [PATCH] Updates to Web testing sections of reference docs Closes gh-19647 --- src/docs/asciidoc/testing-webtestclient.adoc | 150 +++++++++--- src/docs/asciidoc/testing.adoc | 234 +++++++------------ 2 files changed, 201 insertions(+), 183 deletions(-) diff --git a/src/docs/asciidoc/testing-webtestclient.adoc b/src/docs/asciidoc/testing-webtestclient.adoc index 9c39c9cd6d5..940c9456061 100644 --- a/src/docs/asciidoc/testing-webtestclient.adoc +++ b/src/docs/asciidoc/testing-webtestclient.adoc @@ -3,11 +3,11 @@ :doc-root: https://docs.spring.io :api-spring-framework: {doc-root}/spring-framework/docs/{spring-version}/javadoc-api/org/springframework -`WebTestClient` is a thin shell around <>, -using it to perform requests and exposing a dedicated, fluent API for verifying responses. -`WebTestClient` binds to a WebFlux application by using a -<>, or it can test any -web server over an HTTP connection. +`WebTestClient` is an HTTP client designed for testing server applications. It wraps +Spring's <> and uses it to perform requests +but exposes a testing facade for verifying responses. `WebTestClient` can be used to +perform end-to-end HTTP tests. It can also be used to test Spring MVC and Spring WebFlux +applications without a running server via mock server request and response objects. TIP: Kotlin users: See <> related to use of the `WebTestClient`. @@ -18,65 +18,61 @@ related to use of the `WebTestClient`. [[webtestclient-setup]] == Setup -To create a `WebTestClient` you must choose one of several server setup options. -Effectively you're either configuring the WebFlux application to bind to or using -a URL to connect to a running server. +To set up a `WebTestClient` you need to choose a server setup to bind to. This can be one +of several mock server setup choices or a connection to a live server. [[webtestclient-controller-config]] === Bind to Controller -The following example shows how to create a server setup to test one `@Controller` at a time: +This setup allows you to test specific controller(s) via mock request and response objects, +without a running server. + +For WebFlux applications, use the below which loads the +<> and registers the given +controller(s) to handle requests with: [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- - client = WebTestClient.bindToController(new TestController()).build(); + WebTestClient client = + WebTestClient.bindToController(new TestController()).build(); ---- [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] .Kotlin ---- - client = WebTestClient.bindToController(TestController()).build() + val client = WebTestClient.bindToController(TestController()).build() ---- -The preceding example loads the <> -and registers the given controller. The resulting WebFlux application is tested -without an HTTP server by using mock request and response objects. There are more methods -on the builder to customize the default WebFlux Java configuration. - - - -[[webtestclient-fn-config]] -=== Bind to Router Function - -The following example shows how to set up a server from a -<>: +For Spring MVC, use the below which loads infrastructure equivalent to the +<> and registers the given controller(s) to +handle requests with via <>: [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- - RouterFunction route = ... - client = WebTestClient.bindToRouterFunction(route).build(); + WebTestClient client = + MockMvcWebTestClient.bindToController(new TestController()).build(); ---- [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] .Kotlin ---- - val route: RouterFunction<*> = ... - val client = WebTestClient.bindToRouterFunction(route).build() + val client = MockMvcWebTestClient.bindToController(TestController()).build() ---- -Internally, the configuration is passed to `RouterFunctions.toWebHandler`. -The resulting WebFlux application is tested without an HTTP server by using mock -request and response objects. - [[webtestclient-context-config]] === Bind to `ApplicationContext` -The following example shows how to set up a server from the Spring configuration of your -application or some subset of it: +This setup allows you to point to Spring configuration with Spring MVC or Spring WebFlux +infrastructure and controller declarations which is then exercised via mock request and +response objects, without a running server. + +For WebFlux, use the below in which the given Spring `ApplicationContext` is passed to +`WebHttpHandlerBuilder` to create the +<> to handle requests with: [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java @@ -114,10 +110,88 @@ application or some subset of it: <2> Inject the configuration <3> Create the `WebTestClient` -Internally, the configuration is passed to `WebHttpHandlerBuilder` to set up the request -processing chain. See <> for -more details. The resulting WebFlux application is tested without an HTTP server by -using mock request and response objects. +For Spring MVC, use the below in which the given Spring `ApplicationContext` is passed +to `MockMvcBuilders#webAppContextSetup` to create a +<> to handle requests with: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + @ExtendWith(SpringExtension.class) + @WebAppConfiguration("classpath:META-INF/web-resources") // <1> + @ContextHierarchy({ + @ContextConfiguration(classes = RootConfig.class), + @ContextConfiguration(classes = WebConfig.class) + }) + class MyTests { + + @Autowired + private WebApplicationContext wac; // <2> + + WebTestClient client; + + @BeforeEach + void setUp() { + client = MockMvcWebTestClient.bindToApplicationContext(this.wac).build(); // <3> + } + } +---- +<1> Specify the configuration to load +<2> Inject the configuration +<3> Create the `WebTestClient` + +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + @ExtendWith(SpringExtension.class) + @WebAppConfiguration("classpath:META-INF/web-resources") // <1> + @ContextHierarchy({ + @ContextConfiguration(classes = RootConfig.class), + @ContextConfiguration(classes = WebConfig.class) + }) + class MyTests { + + @Autowired + lateinit var wac: WebApplicationContext; // <2> + + lateinit var client: WebTestClient + + @BeforeEach + fun setUp() { // <2> + client = WebTestClient.bindToApplicationContext(context).build() // <3> + } + } +---- +<1> Specify the configuration to load +<2> Inject the configuration +<3> Create the `WebTestClient` + + + +[[webtestclient-fn-config]] +=== Bind to Router Function + +This setup allows you to test <> +via mock request and response objects, without a running server. + +For WebFlux, use the below which delegates to `RouterFunctions.toWebHandler` to create a +server setup to handle requests with: + +[source,java,indent=0,subs="verbatim,quotes",role="primary"] +.Java +---- + RouterFunction route = ... + client = WebTestClient.bindToRouterFunction(route).build(); +---- +[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] +.Kotlin +---- + val route: RouterFunction<*> = ... + val client = WebTestClient.bindToRouterFunction(route).build() +---- + +For Spring MVC there is currently no options to test +<>. @@ -140,7 +214,7 @@ The following server setup option lets you connect to a running server: [[webtestclient-client-config]] -=== Client Builder +=== Client Config In addition to the server setup options described earlier, you can also configure client options, including base URL, default headers, client filters, and others. These options diff --git a/src/docs/asciidoc/testing.adoc b/src/docs/asciidoc/testing.adoc index adabb4fe0fb..930ef36a2f4 100644 --- a/src/docs/asciidoc/testing.adoc +++ b/src/docs/asciidoc/testing.adoc @@ -6488,151 +6488,83 @@ of `AbstractTestNGSpringContextTests` for an example of how to instrument your t -[[spring-mvc-test-framework]] -=== Spring MVC Test Framework - -The Spring MVC Test framework provides first class support for testing Spring MVC code -with a fluent API that you can use with JUnit, TestNG, or any other testing framework. It -is built on the {api-spring-framework}/mock/web/package-summary.html[Servlet API mock objects] -from the `spring-test` module and, hence, does not use a running Servlet container. It -uses the `DispatcherServlet` to provide full Spring MVC runtime behavior and provides -support for loading actual Spring configuration with the TestContext framework in -addition to a standalone mode, in which you can manually instantiate controllers and test -them one at a time. - -Spring MVC Test also provides client-side support for testing code that uses the -`RestTemplate`. Client-side tests mock the server responses and also do not use a running -server. - -TIP: Spring Boot provides an option to write full, end-to-end integration tests that -include a running server. If this is your goal, see the -{doc-spring-boot}/html/spring-boot-features.html#boot-features-testing[Spring Boot Reference Guide]. -For more information on the differences between out-of-container and end-to-end -integration tests, see <>. +include::testing-webtestclient.adoc[leveloffset=+2] -[[spring-mvc-test-server]] -==== Server-Side Tests -You can write a plain unit test for a Spring MVC controller by using JUnit or TestNG. To -do so, instantiate the controller, inject it with mocked or stubbed dependencies, and -call its methods (passing `MockHttpServletRequest`, `MockHttpServletResponse`, and -others, as necessary). However, when writing such a unit test, much remains untested: for -example, request mappings, data binding, type conversion, validation, and much more. -Furthermore, other controller methods such as `@InitBinder`, `@ModelAttribute`, and -`@ExceptionHandler` may also be invoked as part of the request processing lifecycle. +[[spring-mvc-test-framework]] +=== MockMvc -The goal of Spring MVC Test is to provide an effective way to test controllers by -performing requests and generating responses through the actual `DispatcherServlet`. +The Spring MVC Test framework, also known as MockMvc, provides support for testing Spring +MVC applications. It performs full Spring MVC request handling but via mock request and +response objects instead of a running server. -Spring MVC Test builds on the familiar <> available in the `spring-test` module. This allows performing requests -and generating responses without the need for running in a Servlet container. For the -most part, everything should work as it does at runtime with a few notable exceptions, as -explained in <>. The following JUnit -Jupiter-based example uses Spring MVC Test: +MockMvc can be used on its own to perform requests and verify responses. It can also be +used through the <> where MockMvc is plugged in as the server to handle +requests with. The advantage of `WebTestClient` is the option to work with higher level +objects instead of raw data as well as the ability to switch to full, end-to-end HTTP +tests against a live server and use the same test API. -[source,java,indent=0,subs="verbatim,quotes",role="primary"] -.Java ----- - import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; - import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - @SpringJUnitWebConfig(locations = "test-servlet-context.xml") - class ExampleTests { +[[spring-mvc-test-server]] +==== Overview - MockMvc mockMvc; +You can write plain unit tests for Spring MVC by instantiating a controller, injecting it +with dependencies, and calling its methods. However such tests do not verify request +mappings, data binding, message conversion, type conversion, validation, and nor +do they involve any of the supporting `@InitBinder`, `@ModelAttribute`, or +`@ExceptionHandler` methods. - @BeforeEach - void setup(WebApplicationContext wac) { - this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build(); - } +The Spring MVC Test framework, also known as `MockMvc`, aims to provide more complete +testing for Spring MVC controllers without a running server. It does that by invoking +the `DispacherServlet` and passing +<> from the +`spring-test` module which replicates the full Spring MVC request handling without +a running server. - @Test - void getAccount() throws Exception { - this.mockMvc.perform(get("/accounts/1") - .accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(content().contentType("application/json")) - .andExpect(jsonPath("$.name").value("Lee")); - } - } ----- -[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] -.Kotlin ----- - import org.springframework.test.web.servlet.get +MockMvc is a server side test framework that lets you verify most of the functionality +of a Spring MVC application using lightweight and targeted tests. You can use it on +its own to perform requests and to verify responses, or you can also use it through +the <> API with MockMvc plugged in as the server to handle requests +with. - @SpringJUnitWebConfig(locations = ["test-servlet-context.xml"]) - class ExampleTests { - lateinit var mockMvc: MockMvc +[[spring-mvc-test-server-static-imports]] +===== Static Imports - @BeforeEach - fun setup(wac: WebApplicationContext) { - this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build() - } +When using MockMvc directly to perform requests, you'll need static imports for: - @Test - fun getAccount() { - mockMvc.get("/accounts/1") { - accept = MediaType.APPLICATION_JSON - }.andExpect { - status { isOk } - content { contentType(MediaType.APPLICATION_JSON) } - jsonPath("$.name") { value("Lee") } - } - } - } ----- +- `MockMvcBuilders.{asterisk}` +- `MockMvcRequestBuilders.{asterisk}` +- `MockMvcResultMatchers.{asterisk}` +- `MockMvcResultHandlers.{asterisk}` -NOTE: A dedicated <> is available in Kotlin +An easy way to remember that is search for `MockMvc*`. If using Eclipse be sure to also +add the above as "`favorite static members`" in the Eclipse preferences. -The preceding test relies on the `WebApplicationContext` support of the TestContext -framework to load Spring configuration from an XML configuration file located in the same -package as the test class, but Java-based and Groovy-based configuration are also -supported. See these -https://github.com/spring-projects/spring-framework/tree/master/spring-test/src/test/java/org/springframework/test/web/servlet/samples/context[sample tests]. +When using MockMvc through the <> you do not need static imports. +The `WebTestClient` provides a fluent API without static imports. -The `MockMvc` instance is used to perform a `GET` request to `/accounts/1` and verify -that the resulting response has status 200, the content type is `application/json`, and -the response body has a JSON property called `name` with the value `Lee`. The `jsonPath` -syntax is supported through the Jayway https://github.com/jayway/JsonPath[JsonPath -project]. Many other options for verifying the result of the performed request are -discussed later in this document. - -[[spring-mvc-test-server-static-imports]] -===== Static Imports - -The fluent API in the example from the <> -requires a few static imports, such as `MockMvcRequestBuilders.{asterisk}`, -`MockMvcResultMatchers.{asterisk}`, and `MockMvcBuilders.{asterisk}`. An easy way to find -these classes is to search for types that match `MockMvc*`. If you use Eclipse or the -https://spring.io/tools[Spring Tools for Eclipse], be sure to add them as "`favorite static members`" in -the Eclipse preferences under Java -> Editor -> Content Assist -> Favorites. Doing so -lets you use content assist after typing the first character of the static method name. -Other IDEs (such as IntelliJ) may not require any additional configuration. Check the -support for code completion on static members. [[spring-mvc-test-server-setup-options]] ===== Setup Choices -You have two main options for creating an instance of `MockMvc`. The first is to load -Spring MVC configuration through the TestContext framework, which loads the Spring -configuration and injects a `WebApplicationContext` into the test to use to build a -`MockMvc` instance. The following example shows how to do so: +MockMvc can be setup in one of two ways. One is to point directly to the controllers you +want to test and programmatically configure Spring MVC infrastructure. The second is to +point to Spring configuration with Spring MVC and controller infrastructure in it. + +To set up MockMvc for testing a specific controller, use the following: [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- - @SpringJUnitWebConfig(locations = "my-servlet-context.xml") class MyWebTests { MockMvc mockMvc; @BeforeEach - void setup(WebApplicationContext wac) { - this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); + void setup() { + this.mockMvc = MockMvcBuilders.standaloneSetup(new AccountController()).build(); } // ... @@ -6643,14 +6575,13 @@ configuration and injects a `WebApplicationContext` into the test to use to buil [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] .Kotlin ---- - @SpringJUnitWebConfig(locations = ["my-servlet-context.xml"]) class MyWebTests { - lateinit var mockMvc: MockMvc + lateinit var mockMvc : MockMvc @BeforeEach - fun setup(wac: WebApplicationContext) { - mockMvc = MockMvcBuilders.webAppContextSetup(wac).build() + fun setup() { + mockMvc = MockMvcBuilders.standaloneSetup(AccountController()).build() } // ... @@ -6658,21 +6589,23 @@ configuration and injects a `WebApplicationContext` into the test to use to buil } ---- -Your second option is to manually create a controller instance without loading Spring -configuration. Instead, basic default configuration, roughly comparable to that of the -MVC JavaConfig or the MVC namespace, is automatically created. You can customize it to a -degree. The following example shows how to do so: +Or you can also use this setup when testing through the +<> which delegates to the same builder +as shown above. + +To set up MockMvc through Spring configuration, use the following: [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java ---- + @SpringJUnitWebConfig(locations = "my-servlet-context.xml") class MyWebTests { MockMvc mockMvc; @BeforeEach - void setup() { - this.mockMvc = MockMvcBuilders.standaloneSetup(new AccountController()).build(); + void setup(WebApplicationContext wac) { + this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); } // ... @@ -6683,13 +6616,14 @@ degree. The following example shows how to do so: [source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"] .Kotlin ---- + @SpringJUnitWebConfig(locations = ["my-servlet-context.xml"]) class MyWebTests { - lateinit var mockMvc : MockMvc + lateinit var mockMvc: MockMvc @BeforeEach - fun setup() { - mockMvc = MockMvcBuilders.standaloneSetup(AccountController()).build() + fun setup(wac: WebApplicationContext) { + mockMvc = MockMvcBuilders.webAppContextSetup(wac).build() } // ... @@ -6697,6 +6631,12 @@ degree. The following example shows how to do so: } ---- +Or you can also use this setup when testing through the +<> which delegates to the same builder +as shown above. + + + Which setup option should you use? The `webAppContextSetup` loads your actual Spring MVC configuration, resulting in a more @@ -6826,7 +6766,11 @@ for a list of all MockMvc builder features or use the IDE to explore the availab [[spring-mvc-test-server-performing-requests]] ===== Performing Requests -You can perform requests that use any HTTP method, as the following example shows: +This section shows how to use MockMvc on its own to perform requests and verify responses. +If using MockMvc through the `WebTestClient` please see the corresponding section on +<> instead. + +To perform requests that use any HTTP method, as the following example shows: [source,java,indent=0,subs="verbatim,quotes",role="primary"] .Java @@ -7137,6 +7081,11 @@ resulting links by using XPath expressions: [[spring-mvc-test-async-requests]] ===== Async Requests +This section shows how to use MockMvc on its own to test asynchronous request handling. +If using MockMvc through the <>, there is nothing special to do to make +asynchronous requests work as the `WebTestClient` automatically does what is described +in this section. + Servlet 3.0 asynchronous requests, <>, work by exiting the Servlet container thread and allowing the application to compute the response asynchronously, after which @@ -7200,10 +7149,8 @@ or reactive type such as Reactor `Mono`: ===== Streaming Responses There are no options built into Spring MVC Test for container-less testing of streaming -responses. Applications that make use of -<> options can use the -<> to perform end-to-end, integration -tests against a running server. This is also supported in Spring Boot where you can +responses. However you can test streaming requests through the <>. +This is also supported in Spring Boot where you can {doc-spring-boot}/html/spring-boot-features.html#boot-features-testing-spring-boot-applications-testing-with-running-server[test a running server] with `WebTestClient`. One extra advantage is the ability to use the `StepVerifier` from project Reactor that allows declaring expectations on a stream of data. @@ -7231,9 +7178,9 @@ last filter delegates to the `DispatcherServlet`. [[spring-mvc-test-vs-end-to-end-integration-tests]] -===== Spring MVC Test vs End-to-End Tests +===== MockMvc vs End-to-End Tests -Spring MVC Test is built on Servlet API mock implementations from the +MockMVc is built on Servlet API mock implementations from the `spring-test` module and does not rely on a running container. Therefore, there are some differences when compared to full end-to-end integration tests with an actual client and a live server running. @@ -7280,11 +7227,10 @@ of testing even within the same project. ===== Further Examples The framework's own tests include -https://github.com/spring-projects/spring-framework/tree/master/spring-test/src/test/java/org/springframework/test/web/servlet/samples[many -sample tests] intended to show how to use Spring MVC Test. You can browse these examples -for further ideas. Also, the -https://github.com/spring-projects/spring-mvc-showcase[`spring-mvc-showcase`] project has -full test coverage based on Spring MVC Test. +https://github.com/spring-projects/spring-framework/tree/master/spring-test/src/test/java/org/springframework/test/web/servlet/samples[ +many sample tests] intended to show how to use MockMvc on its own or through the +https://github.com/spring-projects/spring-framework/tree/master/spring-test/src/test/java/org/springframework/test/web/servlet/samples/client[ +WebTestClient]. Browse these examples for further ideas. [[spring-mvc-test-server-htmlunit]] @@ -8359,7 +8305,7 @@ http://www.gebish.org/manual/current/[The Book of Geb] user's manual. [[spring-mvc-test-client]] -==== Client-Side REST Tests +=== Testing Client Applications You can use client-side tests to test code that internally uses the `RestTemplate`. The idea is to declare expected requests and to provide "`stub`" responses so that you can @@ -8478,7 +8424,7 @@ logic but without running a server. The following example shows how to do so: ---- [[spring-mvc-test-client-static-imports]] -===== Static Imports +==== Static Imports As with server-side tests, the fluent API for client-side tests requires a few static imports. Those are easy to find by searching for `MockRest*`. Eclipse users should add @@ -8489,14 +8435,12 @@ the static method name. Other IDEs (such IntelliJ) may not require any additiona configuration. Check for the support for code completion on static members. [[spring-mvc-test-client-resources]] -===== Further Examples of Client-side REST Tests +==== Further Examples of Client-side REST Tests Spring MVC Test's own tests include https://github.com/spring-projects/spring-framework/tree/master/spring-test/src/test/java/org/springframework/test/web/client/samples[example tests] of client-side REST tests. -include::testing-webtestclient.adoc[leveloffset=+2] - [[testing-resources]]