Document AssertJ support for MockMvc

This commit restructures the section on MockMvc so that the anchors
are easier to read. The standard integration has moved to a
Hamcrest Integration section  at the same level as HtmlUnit Integration,
and a new AssertJ Integration section has been created.

Closes gh-32454
This commit is contained in:
Stéphane Nicoll
2024-06-21 12:50:36 +02:00
parent 7d236e29bb
commit d43dba63a1
54 changed files with 1432 additions and 272 deletions

View File

@@ -0,0 +1,16 @@
[[mockmvc-tester]]
= AssertJ Integration
:page-section-summary-toc: 1
The AssertJ integration builds on top of plain `MockMvc` with several differences:
* There is no need to use static imports as both the requests and assertions can be
crafted using a fluent API.
* Unresolved exceptions are handled consistently so that your tests do not need to
throw (or catch) `Exception`.
* By default, the result to assert is complete whether the processing is asynchronous
or not. In other words, there is no need for special handling for Async requests.
`MockMvcTester` is the entry point for the AssertJ support. It allows to craft the
request and return a result that is AssertJ compatible so that it can be wrapped in
a standard `assertThat()` method.

View File

@@ -0,0 +1,49 @@
[[mockmvc-tester-assertions]]
= Defining Expectations
Assertions work the same way as any AssertJ assertions. The support provides dedicated
assert objects for the various pieces of the `MvcTestResult`, as shown in the following
example:
include-code::./HotelControllerTests[tag=get,indent=0]
If a request fails, the exchange does not throw the exception. Rather, you can assert
that the result of the exchange has failed:
include-code::./HotelControllerTests[tag=failure,indent=0]
The request could also fail unexpectedly, that is the exception thrown by the handler
has not been handled and is thrown as is. You can still use `.hasFailed()` and
`.failure()` but any attempt to access part of the result will throw an exception as
the exchange hasn't completed.
[[mockmvc-tester-assertions-json]]
== JSON Support
The AssertJ support for `MvcTestResult` provides JSON support via `bodyJson()`.
If https://github.com/jayway/JsonPath[JSONPath] is available, you can apply an expression
on the JSON document. The returned value provides convenient methods to return a dedicated
assert object for the various supported JSON data types:
include-code::./FamilyControllerTests[tag=extract-asmap,indent=0]
You can also convert the raw content to any of your data types as long as the message
converter is configured properly:
include-code::./FamilyControllerTests[tag=extract-convert,indent=0]
Converting to a target `Class` provides a generic assert object. For more complex types,
you may want to use `AssertFactory` instead that returns a dedicated assert type, if
possible:
include-code::./FamilyControllerTests[tag=extract-convert-assert-factory,indent=0]
https://jsonassert.skyscreamer.org[JSONAssert] is also supported. The body of the
response can be matched against a `Resource` or a content. If the content ends with
`.json ` we look for a file matching that name on the classpath:
include-code::./FamilyControllerTests[tag=assert-file,indent=0]
If you prefer to use another library, you can provide an implementation of
{spring-framework-api}/test/json/JsonComparator.html[`JsonComparator`].

View File

@@ -0,0 +1,23 @@
[[mockmvc-tester-integration]]
= MockMvc integration
If you want to use the AssertJ support but have invested in the original `MockMvc`
API, `MockMvcTester` offers several ways to integrate with it.
If you have your own `RequestBuilder` implementation, you can trigger the processing
of the request using `perform`. The example below showcases how the query can be
crafted with the original API:
include-code::./HotelControllerTests[tag=perform,indent=0]
Similarly, if you have crafted custom matchers that you use with the `.andExpect` feature
of `MockMvc` you can use them via `.matches`. In the example below, we rewrite the
preceding example to assert the status with the `ResultMatcher` implementation that
`MockMvc` provides:
include-code::./HotelControllerTests[tag=matches,indent=0]
`MockMvc` also defines a `ResultHandler` contract that lets you execute arbitrary actions
on `MvcResult`. If you have implemented this contract you can invoke it using `.apply`.

View File

@@ -0,0 +1,83 @@
[[mockmvc-tester-requests]]
= Performing Requests
This section shows how to use `MockMvcTester` to perform requests and its integration
with AssertJ to verify responses.
`MockMvcTester` provides a fluent API to compose the request that reuses the same
`MockHttpServletRequestBuilder` as the Hamcrest support, except that there is no need
to import a static method. The builder that is returned is AssertJ-aware so that
wrapping it in the regular `assertThat()` factory method triggers the exchange and
provides access to a dedicated Assert object for `MvcTestResult`.
Here is a simple example that performs a `POST` on `/hotels/42` and configures the
request to specify an `Accept` header:
include-code::./HotelControllerTests[tag=post,indent=0]
AssertJ often consists of multiple `assertThat()` statements to validate the different
parts of the exchange. Rather than having a single statement as in the case above, you
can use `.exchange()` to return a `MvcTestResult` that can be used in multiple
`assertThat` statements:
include-code::./HotelControllerTests[tag=post-exchange,indent=0]
You can specify query parameters in URI template style, as the following example shows:
include-code::./HotelControllerTests[tag=query-parameters,indent=0]
You can also add Servlet request parameters that represent either query or form
parameters, as the following example shows:
include-code::./HotelControllerTests[tag=parameters,indent=0]
If application code relies on Servlet request parameters and does not check the query
string explicitly (as is most often the case), it does not matter which option you use.
Keep in mind, however, that query parameters provided with the URI template are decoded
while request parameters provided through the `param(...)` method are expected to already
be decoded.
[[mockmvc-tester-requests-async]]
== Async
If the processing of the request is done asynchronously, `exchange()` waits for
the completion of the request so that the result to assert is effectively immutable.
The default timeout is 10 seconds but it can be controlled on a request-by-request
basis as shown in the following example:
include-code::./AsyncControllerTests[tag=duration,indent=0]
If you prefer to get the raw result and manage the lifecycle of the asynchronous
request yourself, use `asyncExchange` rather than `exchange`.
[[mockmvc-tester-requests-multipart]]
== Multipart
You can perform file upload requests that internally use
`MockMultipartHttpServletRequest` so that there is no actual parsing of a multipart
request. Rather, you have to set it up to be similar to the following example:
include-code::./MultipartControllerTests[tag=snippet,indent=0]
[[mockmvc-tester-requests-paths]]
== Using Servlet and Context Paths
In most cases, it is preferable to leave the context path and the Servlet path out of the
request URI. If you must test with the full request URI, be sure to set the `contextPath`
and `servletPath` accordingly so that request mappings work, as the following example
shows:
include-code::./HotelControllerTests[tag=context-servlet-paths,indent=0]
In the preceding example, it would be cumbersome to set the `contextPath` and
`servletPath` with every performed request. Instead, you can set up default request
properties, as the following example shows:
include-code::./HotelControllerTests[tag=default-customizations,indent=0]
The preceding properties affect every request performed through the `mockMvc` instance.
If the same property is also specified on a given request, it overrides the default
value. That is why the HTTP method and URI in the default request do not matter, since
they must be specified on every request.

View File

@@ -0,0 +1,30 @@
[[mockmvc-tester-setup]]
= Configuring MockMvcTester
`MockMvcTester` 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.
TIP: For a comparison of those two modes, check xref:testing/mockmvc/setup-options.adoc[Setup Options].
To set up `MockMvcTester` for testing a specific controller, use the following:
include-code::./AccountControllerStandaloneTests[tag=snippet,indent=0]
To set up `MockMvcTester` through Spring configuration, use the following:
include-code::./AccountControllerIntegrationTests[tag=snippet,indent=0]
`MockMvcTester` can convert the JSON response body, or the result of a JSONPath expression,
to one of your domain object as long as the relevant `HttpMessageConverter` is registered.
If you use Jackson to serialize content to JSON, the following example registers the
converter:
include-code::./converter/AccountControllerIntegrationTests[tag=snippet,indent=0]
NOTE: The above assumes the converter has been registered as a Bean.
Finally, if you have a `MockMvc` instance handy, you can create a `MockMvcTester` by
providing the `MockMvc` instance to use using the `create` factory method.

View File

@@ -0,0 +1,7 @@
[[mockmvc-server]]
= Hamcrest Integration
:page-section-summary-toc: 1
Plain `MockMvc` provides an API to build the request using a builder-style approach
that can be initiated with static imports. Hamcrest is used to define expectations and
it provides many out-of-the-box options for common needs.

View File

@@ -0,0 +1,74 @@
[[mockmvc-async-requests]]
= Async Requests
This section shows how to use MockMvc on its own to test asynchronous request handling.
If using MockMvc through the xref:testing/webtestclient.adoc[WebTestClient], there is nothing special to do to make
asynchronous requests work as the `WebTestClient` automatically does what is described
in this section.
Servlet asynchronous requests, xref:web/webmvc/mvc-ann-async.adoc[supported in Spring MVC],
work by exiting the Servlet container thread and allowing the application to compute
the response asynchronously, after which an async dispatch is made to complete
processing on a Servlet container thread.
In Spring MVC Test, async requests can be tested by asserting the produced async value
first, then manually performing the async dispatch, and finally verifying the response.
Below is an example test for controller methods that return `DeferredResult`, `Callable`,
or reactive type such as Reactor `Mono`:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
// static import of MockMvcRequestBuilders.* and MockMvcResultMatchers.*
@Test
void test() throws Exception {
MvcResult mvcResult = this.mockMvc.perform(get("/path"))
.andExpect(status().isOk()) <1>
.andExpect(request().asyncStarted()) <2>
.andExpect(request().asyncResult("body")) <3>
.andReturn();
this.mockMvc.perform(asyncDispatch(mvcResult)) <4>
.andExpect(status().isOk()) <5>
.andExpect(content().string("body"));
}
----
<1> Check response status is still unchanged
<2> Async processing must have started
<3> Wait and assert the async result
<4> Manually perform an ASYNC dispatch (as there is no running container)
<5> Verify the final response
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
@Test
fun test() {
var mvcResult = mockMvc.get("/path").andExpect {
status { isOk() } // <1>
request { asyncStarted() } // <2>
// TODO Remove unused generic parameter
request { asyncResult<Nothing>("body") } // <3>
}.andReturn()
mockMvc.perform(asyncDispatch(mvcResult)) // <4>
.andExpect {
status { isOk() } // <5>
content().string("body")
}
}
----
<1> Check response status is still unchanged
<2> Async processing must have started
<3> Wait and assert the async result
<4> Manually perform an ASYNC dispatch (as there is no running container)
<5> Verify the final response
======

View File

@@ -0,0 +1,251 @@
[[mockmvc-server-defining-expectations]]
= Defining Expectations
You can define expectations by appending one or more `andExpect(..)` calls after
performing a request, as the following example shows. As soon as one expectation fails,
no other expectations will be asserted.
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
// static import of MockMvcRequestBuilders.* and MockMvcResultMatchers.*
mockMvc.perform(get("/accounts/1")).andExpect(status().isOk());
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
import org.springframework.test.web.servlet.get
mockMvc.get("/accounts/1").andExpect {
status { isOk() }
}
----
======
You can define multiple expectations by appending `andExpectAll(..)` after performing a
request, as the following example shows. In contrast to `andExpect(..)`,
`andExpectAll(..)` guarantees that all supplied expectations will be asserted and that
all failures will be tracked and reported.
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
// static import of MockMvcRequestBuilders.* and MockMvcResultMatchers.*
mockMvc.perform(get("/accounts/1")).andExpectAll(
status().isOk(),
content().contentType("application/json;charset=UTF-8"));
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
import org.springframework.test.web.servlet.get
mockMvc.get("/accounts/1").andExpectAll {
status { isOk() }
content { contentType(APPLICATION_JSON) }
}
----
======
`MockMvcResultMatchers.*` provides a number of expectations, some of which are further
nested with more detailed expectations.
Expectations fall in two general categories. The first category of assertions verifies
properties of the response (for example, the response status, headers, and content).
These are the most important results to assert.
The second category of assertions goes beyond the response. These assertions let you
inspect Spring MVC specific aspects, such as which controller method processed the
request, whether an exception was raised and handled, what the content of the model is,
what view was selected, what flash attributes were added, and so on. They also let you
inspect Servlet specific aspects, such as request and session attributes.
The following test asserts that binding or validation failed:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
mockMvc.perform(post("/persons"))
.andExpect(status().isOk())
.andExpect(model().attributeHasErrors("person"));
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
import org.springframework.test.web.servlet.post
mockMvc.post("/persons").andExpect {
status { isOk() }
model {
attributeHasErrors("person")
}
}
----
======
Many times, when writing tests, it is useful to dump the results of the performed
request. You can do so as follows, where `print()` is a static import from
`MockMvcResultHandlers`:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
mockMvc.perform(post("/persons"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(model().attributeHasErrors("person"));
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
import org.springframework.test.web.servlet.post
mockMvc.post("/persons").andDo {
print()
}.andExpect {
status { isOk() }
model {
attributeHasErrors("person")
}
}
----
======
As long as request processing does not cause an unhandled exception, the `print()` method
prints all the available result data to `System.out`. There is also a `log()` method and
two additional variants of the `print()` method, one that accepts an `OutputStream` and
one that accepts a `Writer`. For example, invoking `print(System.err)` prints the result
data to `System.err`, while invoking `print(myWriter)` prints the result data to a custom
writer. If you want to have the result data logged instead of printed, you can invoke the
`log()` method, which logs the result data as a single `DEBUG` message under the
`org.springframework.test.web.servlet.result` logging category.
In some cases, you may want to get direct access to the result and verify something that
cannot be verified otherwise. This can be achieved by appending `.andReturn()` after all
other expectations, as the following example shows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
MvcResult mvcResult = mockMvc.perform(post("/persons")).andExpect(status().isOk()).andReturn();
// ...
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
var mvcResult = mockMvc.post("/persons").andExpect { status { isOk() } }.andReturn()
// ...
----
======
If all tests repeat the same expectations, you can set up common expectations once when
building the `MockMvc` instance, as the following example shows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
standaloneSetup(new SimpleController())
.alwaysExpect(status().isOk())
.alwaysExpect(content().contentType("application/json;charset=UTF-8"))
.build()
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
----
======
Note that common expectations are always applied and cannot be overridden without
creating a separate `MockMvc` instance.
When a JSON response content contains hypermedia links created with
{spring-github-org}/spring-hateoas[Spring HATEOAS], you can verify the
resulting links by using JsonPath expressions, as the following example shows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
mockMvc.perform(get("/people").accept(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.links[?(@.rel == 'self')].href").value("http://localhost:8080/people"));
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
mockMvc.get("/people") {
accept(MediaType.APPLICATION_JSON)
}.andExpect {
jsonPath("$.links[?(@.rel == 'self')].href") {
value("http://localhost:8080/people")
}
}
----
======
When XML response content contains hypermedia links created with
{spring-github-org}/spring-hateoas[Spring HATEOAS], you can verify the
resulting links by using XPath expressions:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
Map<String, String> ns = Collections.singletonMap("ns", "http://www.w3.org/2005/Atom");
mockMvc.perform(get("/handle").accept(MediaType.APPLICATION_XML))
.andExpect(xpath("/person/ns:link[@rel='self']/@href", ns).string("http://localhost:8080/people"));
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
val ns = mapOf("ns" to "http://www.w3.org/2005/Atom")
mockMvc.get("/handle") {
accept(MediaType.APPLICATION_XML)
}.andExpect {
xpath("/person/ns:link[@rel='self']/@href", ns) {
string("http://localhost:8080/people")
}
}
----
======

View File

@@ -0,0 +1,28 @@
[[mockmvc-server-filters]]
= Filter Registrations
:page-section-summary-toc: 1
When setting up a `MockMvc` instance, you can register one or more Servlet `Filter`
instances, as the following example shows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
mockMvc = standaloneSetup(new PersonController()).addFilters(new CharacterEncodingFilter()).build();
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
----
======
Registered filters are invoked through the `MockFilterChain` from `spring-test`, and the
last filter delegates to the `DispatcherServlet`.

View File

@@ -0,0 +1,170 @@
[[mockmvc-server-performing-requests]]
= Performing Requests
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
xref:testing/webtestclient.adoc#webtestclient-tests[Writing Tests] instead.
To perform requests that use any HTTP method, as the following example shows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
// static import of MockMvcRequestBuilders.*
mockMvc.perform(post("/hotels/{id}", 42).accept(MediaType.APPLICATION_JSON));
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
import org.springframework.test.web.servlet.post
mockMvc.post("/hotels/{id}", 42) {
accept = MediaType.APPLICATION_JSON
}
----
======
You can also perform file upload requests that internally use
`MockMultipartHttpServletRequest` so that there is no actual parsing of a multipart
request. Rather, you have to set it up to be similar to the following example:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
mockMvc.perform(multipart("/doc").file("a1", "ABC".getBytes("UTF-8")));
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
import org.springframework.test.web.servlet.multipart
mockMvc.multipart("/doc") {
file("a1", "ABC".toByteArray(charset("UTF8")))
}
----
======
You can specify query parameters in URI template style, as the following example shows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
mockMvc.perform(get("/hotels?thing={thing}", "somewhere"));
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
mockMvc.get("/hotels?thing={thing}", "somewhere")
----
======
You can also add Servlet request parameters that represent either query or form
parameters, as the following example shows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
mockMvc.perform(get("/hotels").param("thing", "somewhere"));
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
import org.springframework.test.web.servlet.get
mockMvc.get("/hotels") {
param("thing", "somewhere")
}
----
======
If application code relies on Servlet request parameters and does not check the query
string explicitly (as is most often the case), it does not matter which option you use.
Keep in mind, however, that query parameters provided with the URI template are decoded
while request parameters provided through the `param(...)` method are expected to already
be decoded.
In most cases, it is preferable to leave the context path and the Servlet path out of the
request URI. If you must test with the full request URI, be sure to set the `contextPath`
and `servletPath` accordingly so that request mappings work, as the following example
shows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
mockMvc.perform(get("/app/main/hotels/{id}").contextPath("/app").servletPath("/main"))
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
import org.springframework.test.web.servlet.get
mockMvc.get("/app/main/hotels/{id}") {
contextPath = "/app"
servletPath = "/main"
}
----
======
In the preceding example, it would be cumbersome to set the `contextPath` and
`servletPath` with every performed request. Instead, you can set up default request
properties, as the following example shows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
class MyWebTests {
MockMvc mockMvc;
@BeforeEach
void setup() {
mockMvc = standaloneSetup(new AccountController())
.defaultRequest(get("/")
.contextPath("/app").servletPath("/main")
.accept(MediaType.APPLICATION_JSON)).build();
}
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
----
======
The preceding properties affect every request performed through the `MockMvc` instance.
If the same property is also specified on a given request, it overrides the default
value. That is why the HTTP method and URI in the default request do not matter, since
they must be specified on every request.

View File

@@ -0,0 +1,63 @@
[[mockmvc-server-setup-steps]]
= Setup Features
No matter which MockMvc builder you use, all `MockMvcBuilder` implementations provide
some common and very useful features. For example, you can declare an `Accept` header for
all requests and expect a status of 200 as well as a `Content-Type` header in all
responses, as follows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
// static import of MockMvcBuilders.standaloneSetup
MockMvc mockMvc = standaloneSetup(new MusicController())
.defaultRequest(get("/").accept(MediaType.APPLICATION_JSON))
.alwaysExpect(status().isOk())
.alwaysExpect(content().contentType("application/json;charset=UTF-8"))
.build();
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
----
======
In addition, third-party frameworks (and applications) can pre-package setup
instructions, such as those in a `MockMvcConfigurer`. The Spring Framework has one such
built-in implementation that helps to save and re-use the HTTP session across requests.
You can use it as follows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
// static import of SharedHttpSessionConfigurer.sharedHttpSession
MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new TestController())
.apply(sharedHttpSession())
.build();
// Use mockMvc to perform requests...
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
----
======
See the javadoc for
{spring-framework-api}/test/web/servlet/setup/ConfigurableMockMvcBuilder.html[`ConfigurableMockMvcBuilder`]
for a list of all MockMvc builder features or use the IDE to explore the available options.

View File

@@ -0,0 +1,100 @@
[[mockmvc-setup]]
= Configuring MockMvc
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.
TIP: For a comparison of those two modes, check xref:testing/mockmvc/setup-options.adoc[Setup Options].
To set up MockMvc for testing a specific controller, use the following:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
class MyWebTests {
MockMvc mockMvc;
@BeforeEach
void setup() {
this.mockMvc = MockMvcBuilders.standaloneSetup(new AccountController()).build();
}
// ...
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
class MyWebTests {
lateinit var mockMvc : MockMvc
@BeforeEach
fun setup() {
mockMvc = MockMvcBuilders.standaloneSetup(AccountController()).build()
}
// ...
}
----
======
Or you can also use this setup when testing through the
xref:testing/webtestclient.adoc#webtestclient-controller-config[WebTestClient] which delegates to the same builder
as shown above.
To set up MockMvc through Spring configuration, use the following:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
@SpringJUnitWebConfig(locations = "my-servlet-context.xml")
class MyWebTests {
MockMvc mockMvc;
@BeforeEach
void setup(WebApplicationContext wac) {
this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
// ...
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
@SpringJUnitWebConfig(locations = ["my-servlet-context.xml"])
class MyWebTests {
lateinit var mockMvc: MockMvc
@BeforeEach
fun setup(wac: WebApplicationContext) {
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build()
}
// ...
}
----
======
Or you can also use this setup when testing through the
xref:testing/webtestclient.adoc#webtestclient-context-config[WebTestClient] which delegates to the same builder
as shown above.

View File

@@ -0,0 +1,18 @@
[[mockmvc-server-static-imports]]
= Static Imports
:page-section-summary-toc: 1
When using MockMvc directly to perform requests, you'll need static imports for:
- `MockMvcBuilders.{asterisk}`
- `MockMvcRequestBuilders.{asterisk}`
- `MockMvcResultMatchers.{asterisk}`
- `MockMvcResultHandlers.{asterisk}`
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.
When using MockMvc through the xref:testing/webtestclient.adoc[WebTestClient] you do not need static imports.
The `WebTestClient` provides a fluent API without static imports.

View File

@@ -0,0 +1,16 @@
[[mockmvc-vs-streaming-response]]
= Streaming Responses
You can use `WebTestClient` to test xref:testing/webtestclient.adoc#webtestclient-stream[streaming responses]
such as Server-Sent Events. However, `MockMvcWebTestClient` doesn't support infinite
streams because there is no way to cancel the server stream from the client side.
To test infinite streams, you'll need to
xref:testing/webtestclient.adoc#webtestclient-server-config[bind to] a running server,
or when using Spring Boot,
{spring-boot-docs}/spring-boot-features.html#boot-features-testing-spring-boot-applications-testing-with-running-server[test with a running server].
`MockMvcWebTestClient` does support asynchronous responses, and even streaming responses.
The limitation is that it can't influence the server to stop, and therefore the server
must finish writing the response on its own.

View File

@@ -0,0 +1,21 @@
[[mockmvc-server-htmlunit]]
= HtmlUnit Integration
:page-section-summary-toc: 1
Spring provides integration between xref:testing/mockmvc/overview.adoc[MockMvc] and
https://htmlunit.sourceforge.io/[HtmlUnit]. This simplifies performing end-to-end testing
when using HTML-based views. This integration lets you:
* Easily test HTML pages by using tools such as
https://htmlunit.sourceforge.io/[HtmlUnit],
https://www.seleniumhq.org[WebDriver], and
https://www.gebish.org/manual/current/#spock-junit-testng[Geb] without the need to
deploy to a Servlet container.
* Test JavaScript within pages.
* Optionally, test using mock services to speed up testing.
* Share logic between in-container end-to-end tests and out-of-container integration tests.
NOTE: MockMvc works with templating technologies that do not rely on a Servlet Container
(for example, Thymeleaf, FreeMarker, and others), but it does not work with JSPs, since
they rely on the Servlet container.

View File

@@ -0,0 +1,125 @@
[[mockmvc-server-htmlunit-geb]]
= MockMvc and Geb
In the previous section, we saw how to use MockMvc with WebDriver. In this section, we
use https://www.gebish.org/[Geb] to make our tests even Groovy-er.
[[mockmvc-server-htmlunit-geb-why]]
== Why Geb and MockMvc?
Geb is backed by WebDriver, so it offers many of the
xref:testing/mockmvc/htmlunit/webdriver.adoc#spring-mvc-test-server-htmlunit-webdriver-why[same benefits] that we get from
WebDriver. However, Geb makes things even easier by taking care of some of the
boilerplate code for us.
[[mockmvc-server-htmlunit-geb-setup]]
== MockMvc and Geb Setup
We can easily initialize a Geb `Browser` with a Selenium WebDriver that uses MockMvc, as
follows:
[source,groovy]
----
def setup() {
browser.driver = MockMvcHtmlUnitDriverBuilder
.webAppContextSetup(context)
.build()
}
----
NOTE: This is a simple example of using `MockMvcHtmlUnitDriverBuilder`. For more advanced
usage, see xref:testing/mockmvc/htmlunit/webdriver.adoc#spring-mvc-test-server-htmlunit-webdriver-advanced-builder[Advanced `MockMvcHtmlUnitDriverBuilder`].
This ensures that any URL referencing `localhost` as the server is directed to our
`MockMvc` instance without the need for a real HTTP connection. Any other URL is
requested by using a network connection as normal. This lets us easily test the use of
CDNs.
[[mockmvc-server-htmlunit-geb-usage]]
== MockMvc and Geb Usage
Now we can use Geb as we normally would but without the need to deploy our application to
a Servlet container. For example, we can request the view to create a message with the
following:
[source,groovy]
----
to CreateMessagePage
----
We can then fill out the form and submit it to create a message, as follows:
[source,groovy]
----
when:
form.summary = expectedSummary
form.text = expectedMessage
submit.click(ViewMessagePage)
----
Any unrecognized method calls or property accesses or references that are not found are
forwarded to the current page object. This removes a lot of the boilerplate code we
needed when using WebDriver directly.
As with direct WebDriver usage, this improves on the design of our
xref:testing/mockmvc/htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-usage[HtmlUnit test] by using the Page Object
Pattern. As mentioned previously, we can use the Page Object Pattern with HtmlUnit and
WebDriver, but it is even easier with Geb. Consider our new Groovy-based
`CreateMessagePage` implementation:
[source,groovy]
----
class CreateMessagePage extends Page {
static url = 'messages/form'
static at = { assert title == 'Messages : Create'; true }
static content = {
submit { $('input[type=submit]') }
form { $('form') }
errors(required:false) { $('label.error, .alert-error')?.text() }
}
}
----
Our `CreateMessagePage` extends `Page`. We do not go over the details of `Page`, but, in
summary, it contains common functionality for all of our pages. We define a URL in which
this page can be found. This lets us navigate to the page, as follows:
[source,groovy]
----
to CreateMessagePage
----
We also have an `at` closure that determines if we are at the specified page. It should
return `true` if we are on the correct page. This is why we can assert that we are on the
correct page, as follows:
[source,groovy]
----
then:
at CreateMessagePage
errors.contains('This field is required.')
----
NOTE: We use an assertion in the closure so that we can determine where things went wrong
if we were at the wrong page.
Next, we create a `content` closure that specifies all the areas of interest within the
page. We can use a
https://www.gebish.org/manual/current/#the-jquery-ish-navigator-api[jQuery-ish Navigator
API] to select the content in which we are interested.
Finally, we can verify that a new message was created successfully, as follows:
[source,groovy]
----
then:
at ViewMessagePage
success == 'Successfully created a new message'
id
date
summary == expectedSummary
message == expectedMessage
----
For further details on how to get the most out of Geb, see
https://www.gebish.org/manual/current/[The Book of Geb] user's manual.

View File

@@ -0,0 +1,279 @@
[[mockmvc-server-htmlunit-mah]]
= MockMvc and HtmlUnit
This section describes how to integrate MockMvc and HtmlUnit. Use this option if you want
to use the raw HtmlUnit libraries.
[[mockmvc-server-htmlunit-mah-setup]]
== MockMvc and HtmlUnit Setup
First, make sure that you have included a test dependency on
`org.htmlunit:htmlunit`.
We can easily create an HtmlUnit `WebClient` that integrates with MockMvc by using the
`MockMvcWebClientBuilder`, as follows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
WebClient webClient;
@BeforeEach
void setup(WebApplicationContext context) {
webClient = MockMvcWebClientBuilder
.webAppContextSetup(context)
.build();
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
lateinit var webClient: WebClient
@BeforeEach
fun setup(context: WebApplicationContext) {
webClient = MockMvcWebClientBuilder
.webAppContextSetup(context)
.build()
}
----
======
NOTE: This is a simple example of using `MockMvcWebClientBuilder`. For advanced usage,
see xref:testing/mockmvc/htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-advanced-builder[Advanced `MockMvcWebClientBuilder`].
This ensures that any URL that references `localhost` as the server is directed to our
`MockMvc` instance without the need for a real HTTP connection. Any other URL is
requested by using a network connection, as normal. This lets us easily test the use of
CDNs.
[[mockmvc-server-htmlunit-mah-usage]]
== MockMvc and HtmlUnit Usage
Now we can use HtmlUnit as we normally would but without the need to deploy our
application to a Servlet container. For example, we can request the view to create a
message with the following:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
HtmlPage createMsgFormPage = webClient.getPage("http://localhost/messages/form");
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
val createMsgFormPage = webClient.getPage("http://localhost/messages/form")
----
======
NOTE: The default context path is `""`. Alternatively, we can specify the context path,
as described in xref:testing/mockmvc/htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-advanced-builder[Advanced `MockMvcWebClientBuilder`].
Once we have a reference to the `HtmlPage`, we can then fill out the form and submit it
to create a message, as the following example shows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
HtmlForm form = createMsgFormPage.getHtmlElementById("messageForm");
HtmlTextInput summaryInput = createMsgFormPage.getHtmlElementById("summary");
summaryInput.setValueAttribute("Spring Rocks");
HtmlTextArea textInput = createMsgFormPage.getHtmlElementById("text");
textInput.setText("In case you didn't know, Spring Rocks!");
HtmlSubmitInput submit = form.getOneHtmlElementByAttribute("input", "type", "submit");
HtmlPage newMessagePage = submit.click();
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
val form = createMsgFormPage.getHtmlElementById("messageForm")
val summaryInput = createMsgFormPage.getHtmlElementById("summary")
summaryInput.setValueAttribute("Spring Rocks")
val textInput = createMsgFormPage.getHtmlElementById("text")
textInput.setText("In case you didn't know, Spring Rocks!")
val submit = form.getOneHtmlElementByAttribute("input", "type", "submit")
val newMessagePage = submit.click()
----
======
Finally, we can verify that a new message was created successfully. The following
assertions use the {assertj-docs}[AssertJ] library:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
assertThat(newMessagePage.getUrl().toString()).endsWith("/messages/123");
String id = newMessagePage.getHtmlElementById("id").getTextContent();
assertThat(id).isEqualTo("123");
String summary = newMessagePage.getHtmlElementById("summary").getTextContent();
assertThat(summary).isEqualTo("Spring Rocks");
String text = newMessagePage.getHtmlElementById("text").getTextContent();
assertThat(text).isEqualTo("In case you didn't know, Spring Rocks!");
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
assertThat(newMessagePage.getUrl().toString()).endsWith("/messages/123")
val id = newMessagePage.getHtmlElementById("id").getTextContent()
assertThat(id).isEqualTo("123")
val summary = newMessagePage.getHtmlElementById("summary").getTextContent()
assertThat(summary).isEqualTo("Spring Rocks")
val text = newMessagePage.getHtmlElementById("text").getTextContent()
assertThat(text).isEqualTo("In case you didn't know, Spring Rocks!")
----
======
The preceding code improves on our
xref:testing/mockmvc/htmlunit/why.adoc#spring-mvc-test-server-htmlunit-mock-mvc-test[MockMvc test] in a number of ways.
First, we no longer have to explicitly verify our form and then create a request that
looks like the form. Instead, we request the form, fill it out, and submit it, thereby
significantly reducing the overhead.
Another important factor is that https://htmlunit.sourceforge.io/javascript.html[HtmlUnit
uses the Mozilla Rhino engine] to evaluate JavaScript. This means that we can also test
the behavior of JavaScript within our pages.
See the https://htmlunit.sourceforge.io/gettingStarted.html[HtmlUnit documentation] for
additional information about using HtmlUnit.
[[mockmvc-server-htmlunit-mah-advanced-builder]]
== Advanced `MockMvcWebClientBuilder`
In the examples so far, we have used `MockMvcWebClientBuilder` in the simplest way
possible, by building a `WebClient` based on the `WebApplicationContext` loaded for us by
the Spring TestContext Framework. This approach is repeated in the following example:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
WebClient webClient;
@BeforeEach
void setup(WebApplicationContext context) {
webClient = MockMvcWebClientBuilder
.webAppContextSetup(context)
.build();
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
lateinit var webClient: WebClient
@BeforeEach
fun setup(context: WebApplicationContext) {
webClient = MockMvcWebClientBuilder
.webAppContextSetup(context)
.build()
}
----
======
We can also specify additional configuration options, as the following example shows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
WebClient webClient;
@BeforeEach
void setup() {
webClient = MockMvcWebClientBuilder
// demonstrates applying a MockMvcConfigurer (Spring Security)
.webAppContextSetup(context, springSecurity())
// for illustration only - defaults to ""
.contextPath("")
// By default MockMvc is used for localhost only;
// the following will use MockMvc for example.com and example.org as well
.useMockMvcForHosts("example.com","example.org")
.build();
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
lateinit var webClient: WebClient
@BeforeEach
fun setup() {
webClient = MockMvcWebClientBuilder
// demonstrates applying a MockMvcConfigurer (Spring Security)
.webAppContextSetup(context, springSecurity())
// for illustration only - defaults to ""
.contextPath("")
// By default MockMvc is used for localhost only;
// the following will use MockMvc for example.com and example.org as well
.useMockMvcForHosts("example.com","example.org")
.build()
}
----
======
As an alternative, we can perform the exact same setup by configuring the `MockMvc`
instance separately and supplying it to the `MockMvcWebClientBuilder`, as follows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
MockMvc mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
webClient = MockMvcWebClientBuilder
.mockMvcSetup(mockMvc)
// for illustration only - defaults to ""
.contextPath("")
// By default MockMvc is used for localhost only;
// the following will use MockMvc for example.com and example.org as well
.useMockMvcForHosts("example.com","example.org")
.build();
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
----
======
This is more verbose, but, by building the `WebClient` with a `MockMvc` instance, we have
the full power of MockMvc at our fingertips.
TIP: For additional information on creating a `MockMvc` instance, see
xref:testing/mockmvc/hamcrest/setup.adoc[Configuring MockMvc].

View File

@@ -0,0 +1,574 @@
[[mockmvc-server-htmlunit-webdriver]]
= MockMvc and WebDriver
In the previous sections, we have seen how to use MockMvc in conjunction with the raw
HtmlUnit APIs. In this section, we use additional abstractions within the Selenium
https://docs.seleniumhq.org/projects/webdriver/[WebDriver] to make things even easier.
[[mockmvc-server-htmlunit-webdriver-why]]
== Why WebDriver and MockMvc?
We can already use HtmlUnit and MockMvc, so why would we want to use WebDriver? The
Selenium WebDriver provides a very elegant API that lets us easily organize our code. To
better show how it works, we explore an example in this section.
NOTE: Despite being a part of https://docs.seleniumhq.org/[Selenium], WebDriver does not
require a Selenium Server to run your tests.
Suppose we need to ensure that a message is created properly. The tests involve finding
the HTML form input elements, filling them out, and making various assertions.
This approach results in numerous separate tests because we want to test error conditions
as well. For example, we want to ensure that we get an error if we fill out only part of
the form. If we fill out the entire form, the newly created message should be displayed
afterwards.
If one of the fields were named "`summary`", we might have something that resembles the
following repeated in multiple places within our tests:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary");
summaryInput.setValueAttribute(summary);
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
val summaryInput = currentPage.getHtmlElementById("summary")
summaryInput.setValueAttribute(summary)
----
======
So what happens if we change the `id` to `smmry`? Doing so would force us to update all
of our tests to incorporate this change. This violates the DRY principle, so we should
ideally extract this code into its own method, as follows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
public HtmlPage createMessage(HtmlPage currentPage, String summary, String text) {
setSummary(currentPage, summary);
// ...
}
public void setSummary(HtmlPage currentPage, String summary) {
HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary");
summaryInput.setValueAttribute(summary);
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
fun createMessage(currentPage: HtmlPage, summary:String, text:String) :HtmlPage{
setSummary(currentPage, summary);
// ...
}
fun setSummary(currentPage:HtmlPage , summary: String) {
val summaryInput = currentPage.getHtmlElementById("summary")
summaryInput.setValueAttribute(summary)
}
----
======
Doing so ensures that we do not have to update all of our tests if we change the UI.
We might even take this a step further and place this logic within an `Object` that
represents the `HtmlPage` we are currently on, as the following example shows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
public class CreateMessagePage {
final HtmlPage currentPage;
final HtmlTextInput summaryInput;
final HtmlSubmitInput submit;
public CreateMessagePage(HtmlPage currentPage) {
this.currentPage = currentPage;
this.summaryInput = currentPage.getHtmlElementById("summary");
this.submit = currentPage.getHtmlElementById("submit");
}
public <T> T createMessage(String summary, String text) throws Exception {
setSummary(summary);
HtmlPage result = submit.click();
boolean error = CreateMessagePage.at(result);
return (T) (error ? new CreateMessagePage(result) : new ViewMessagePage(result));
}
public void setSummary(String summary) throws Exception {
summaryInput.setValueAttribute(summary);
}
public static boolean at(HtmlPage page) {
return "Create Message".equals(page.getTitleText());
}
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
class CreateMessagePage(private val currentPage: HtmlPage) {
val summaryInput: HtmlTextInput = currentPage.getHtmlElementById("summary")
val submit: HtmlSubmitInput = currentPage.getHtmlElementById("submit")
fun <T> createMessage(summary: String, text: String): T {
setSummary(summary)
val result = submit.click()
val error = at(result)
return (if (error) CreateMessagePage(result) else ViewMessagePage(result)) as T
}
fun setSummary(summary: String) {
summaryInput.setValueAttribute(summary)
}
fun at(page: HtmlPage): Boolean {
return "Create Message" == page.getTitleText()
}
}
}
----
======
Formerly, this pattern was known as the
https://github.com/SeleniumHQ/selenium/wiki/PageObjects[Page Object Pattern]. While we
can certainly do this with HtmlUnit, WebDriver provides some tools that we explore in the
following sections to make this pattern much easier to implement.
[[mockmvc-server-htmlunit-webdriver-setup]]
== MockMvc and WebDriver Setup
To use Selenium WebDriver with `MockMvc`, make sure that your project includes a test
dependency on `org.seleniumhq.selenium:selenium-htmlunit3-driver`.
We can easily create a Selenium WebDriver that integrates with MockMvc by using the
`MockMvcHtmlUnitDriverBuilder` as the following example shows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
WebDriver driver;
@BeforeEach
void setup(WebApplicationContext context) {
driver = MockMvcHtmlUnitDriverBuilder
.webAppContextSetup(context)
.build();
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
lateinit var driver: WebDriver
@BeforeEach
fun setup(context: WebApplicationContext) {
driver = MockMvcHtmlUnitDriverBuilder
.webAppContextSetup(context)
.build()
}
----
======
NOTE: This is a simple example of using `MockMvcHtmlUnitDriverBuilder`. For more advanced
usage, see xref:testing/mockmvc/htmlunit/webdriver.adoc#spring-mvc-test-server-htmlunit-webdriver-advanced-builder[Advanced `MockMvcHtmlUnitDriverBuilder`].
The preceding example ensures that any URL that references `localhost` as the server is
directed to our `MockMvc` instance without the need for a real HTTP connection. Any other
URL is requested by using a network connection, as normal. This lets us easily test the
use of CDNs.
[[mockmvc-server-htmlunit-webdriver-usage]]
== MockMvc and WebDriver Usage
Now we can use WebDriver as we normally would but without the need to deploy our
application to a Servlet container. For example, we can request the view to create a
message with the following:
--
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
CreateMessagePage page = CreateMessagePage.to(driver);
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
val page = CreateMessagePage.to(driver)
----
======
--
We can then fill out the form and submit it to create a message, as follows:
--
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
ViewMessagePage viewMessagePage =
page.createMessage(ViewMessagePage.class, expectedSummary, expectedText);
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
val viewMessagePage =
page.createMessage(ViewMessagePage::class, expectedSummary, expectedText)
----
======
--
This improves on the design of our xref:testing/mockmvc/htmlunit/mah.adoc#spring-mvc-test-server-htmlunit-mah-usage[HtmlUnit test]
by leveraging the Page Object Pattern. As we mentioned in
xref:testing/mockmvc/htmlunit/webdriver.adoc#spring-mvc-test-server-htmlunit-webdriver-why[Why WebDriver and MockMvc?], we can use the Page Object Pattern
with HtmlUnit, but it is much easier with WebDriver. Consider the following
`CreateMessagePage` implementation:
--
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
public class CreateMessagePage extends AbstractPage { // <1>
// <2>
private WebElement summary;
private WebElement text;
@FindBy(css = "input[type=submit]") // <3>
private WebElement submit;
public CreateMessagePage(WebDriver driver) {
super(driver);
}
public <T> T createMessage(Class<T> resultPage, String summary, String details) {
this.summary.sendKeys(summary);
this.text.sendKeys(details);
this.submit.click();
return PageFactory.initElements(driver, resultPage);
}
public static CreateMessagePage to(WebDriver driver) {
driver.get("http://localhost:9990/mail/messages/form");
return PageFactory.initElements(driver, CreateMessagePage.class);
}
}
----
<1> `CreateMessagePage` extends the `AbstractPage`. We do not go over the details of
`AbstractPage`, but, in summary, it contains common functionality for all of our pages.
For example, if our application has a navigational bar, global error messages, and other
features, we can place this logic in a shared location.
<2> We have a member variable for each of the parts of the HTML page in which we are
interested. These are of type `WebElement`. WebDriver's
https://github.com/SeleniumHQ/selenium/wiki/PageFactory[`PageFactory`] lets us remove a
lot of code from the HtmlUnit version of `CreateMessagePage` by automatically resolving
each `WebElement`. The
https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/support/PageFactory.html#initElements-org.openqa.selenium.WebDriver-java.lang.Class-[`PageFactory#initElements(WebDriver,Class<T>)`]
method automatically resolves each `WebElement` by using the field name and looking it up
by the `id` or `name` of the element within the HTML page.
<3> We can use the
https://github.com/SeleniumHQ/selenium/wiki/PageFactory#making-the-example-work-using-annotations[`@FindBy` annotation]
to override the default lookup behavior. Our example shows how to use the `@FindBy`
annotation to look up our submit button with a `css` selector (`input[type=submit]`).
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
class CreateMessagePage(private val driver: WebDriver) : AbstractPage(driver) { // <1>
// <2>
private lateinit var summary: WebElement
private lateinit var text: WebElement
@FindBy(css = "input[type=submit]") // <3>
private lateinit var submit: WebElement
fun <T> createMessage(resultPage: Class<T>, summary: String, details: String): T {
this.summary.sendKeys(summary)
text.sendKeys(details)
submit.click()
return PageFactory.initElements(driver, resultPage)
}
companion object {
fun to(driver: WebDriver): CreateMessagePage {
driver.get("http://localhost:9990/mail/messages/form")
return PageFactory.initElements(driver, CreateMessagePage::class.java)
}
}
}
----
<1> `CreateMessagePage` extends the `AbstractPage`. We do not go over the details of
`AbstractPage`, but, in summary, it contains common functionality for all of our pages.
For example, if our application has a navigational bar, global error messages, and other
features, we can place this logic in a shared location.
<2> We have a member variable for each of the parts of the HTML page in which we are
interested. These are of type `WebElement`. WebDriver's
https://github.com/SeleniumHQ/selenium/wiki/PageFactory[`PageFactory`] lets us remove a
lot of code from the HtmlUnit version of `CreateMessagePage` by automatically resolving
each `WebElement`. The
https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/support/PageFactory.html#initElements-org.openqa.selenium.WebDriver-java.lang.Class-[`PageFactory#initElements(WebDriver,Class<T>)`]
method automatically resolves each `WebElement` by using the field name and looking it up
by the `id` or `name` of the element within the HTML page.
<3> We can use the
https://github.com/SeleniumHQ/selenium/wiki/PageFactory#making-the-example-work-using-annotations[`@FindBy` annotation]
to override the default lookup behavior. Our example shows how to use the `@FindBy`
annotation to look up our submit button with a `css` selector (*input[type=submit]*).
======
--
Finally, we can verify that a new message was created successfully. The following
assertions use the {assertj-docs}[AssertJ] assertion library:
--
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
assertThat(viewMessagePage.getMessage()).isEqualTo(expectedMessage);
assertThat(viewMessagePage.getSuccess()).isEqualTo("Successfully created a new message");
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
assertThat(viewMessagePage.message).isEqualTo(expectedMessage)
assertThat(viewMessagePage.success).isEqualTo("Successfully created a new message")
----
======
--
We can see that our `ViewMessagePage` lets us interact with our custom domain model. For
example, it exposes a method that returns a `Message` object:
--
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
public Message getMessage() throws ParseException {
Message message = new Message();
message.setId(getId());
message.setCreated(getCreated());
message.setSummary(getSummary());
message.setText(getText());
return message;
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
fun getMessage() = Message(getId(), getCreated(), getSummary(), getText())
----
======
--
We can then use the rich domain objects in our assertions.
Lastly, we must not forget to close the `WebDriver` instance when the test is complete,
as follows:
--
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
@AfterEach
void destroy() {
if (driver != null) {
driver.close();
}
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
@AfterEach
fun destroy() {
if (driver != null) {
driver.close()
}
}
----
======
--
For additional information on using WebDriver, see the Selenium
https://github.com/SeleniumHQ/selenium/wiki/Getting-Started[WebDriver documentation].
[[mockmvc-server-htmlunit-webdriver-advanced-builder]]
== Advanced `MockMvcHtmlUnitDriverBuilder`
In the examples so far, we have used `MockMvcHtmlUnitDriverBuilder` in the simplest way
possible, by building a `WebDriver` based on the `WebApplicationContext` loaded for us by
the Spring TestContext Framework. This approach is repeated here, as follows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
WebDriver driver;
@BeforeEach
void setup(WebApplicationContext context) {
driver = MockMvcHtmlUnitDriverBuilder
.webAppContextSetup(context)
.build();
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
lateinit var driver: WebDriver
@BeforeEach
fun setup(context: WebApplicationContext) {
driver = MockMvcHtmlUnitDriverBuilder
.webAppContextSetup(context)
.build()
}
----
======
We can also specify additional configuration options, as follows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
WebDriver driver;
@BeforeEach
void setup() {
driver = MockMvcHtmlUnitDriverBuilder
// demonstrates applying a MockMvcConfigurer (Spring Security)
.webAppContextSetup(context, springSecurity())
// for illustration only - defaults to ""
.contextPath("")
// By default MockMvc is used for localhost only;
// the following will use MockMvc for example.com and example.org as well
.useMockMvcForHosts("example.com","example.org")
.build();
}
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
lateinit var driver: WebDriver
@BeforeEach
fun setup() {
driver = MockMvcHtmlUnitDriverBuilder
// demonstrates applying a MockMvcConfigurer (Spring Security)
.webAppContextSetup(context, springSecurity())
// for illustration only - defaults to ""
.contextPath("")
// By default MockMvc is used for localhost only;
// the following will use MockMvc for example.com and example.org as well
.useMockMvcForHosts("example.com","example.org")
.build()
}
----
======
As an alternative, we can perform the exact same setup by configuring the `MockMvc`
instance separately and supplying it to the `MockMvcHtmlUnitDriverBuilder`, as follows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
MockMvc mockMvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
driver = MockMvcHtmlUnitDriverBuilder
.mockMvcSetup(mockMvc)
// for illustration only - defaults to ""
.contextPath("")
// By default MockMvc is used for localhost only;
// the following will use MockMvc for example.com and example.org as well
.useMockMvcForHosts("example.com","example.org")
.build();
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
// Not possible in Kotlin until {kotlin-issues}/KT-22208 is fixed
----
======
This is more verbose, but, by building the `WebDriver` with a `MockMvc` instance, we have
the full power of MockMvc at our fingertips.
TIP: For additional information on creating a `MockMvc` instance, see
xref:testing/mockmvc/hamcrest/setup.adoc[Configuring MockMvc].

View File

@@ -0,0 +1,203 @@
[[mockmvc-server-htmlunit-why]]
= Why HtmlUnit Integration?
The most obvious question that comes to mind is "`Why do I need this?`" The answer is
best found by exploring a very basic sample application. Assume you have a Spring MVC web
application that supports CRUD operations on a `Message` object. The application also
supports paging through all messages. How would you go about testing it?
With Spring MVC Test, we can easily test if we are able to create a `Message`, as follows:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
MockHttpServletRequestBuilder createMessage = post("/messages/")
.param("summary", "Spring Rocks")
.param("text", "In case you didn't know, Spring Rocks!");
mockMvc.perform(createMessage)
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/messages/123"));
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
@Test
fun test() {
mockMvc.post("/messages/") {
param("summary", "Spring Rocks")
param("text", "In case you didn't know, Spring Rocks!")
}.andExpect {
status().is3xxRedirection()
redirectedUrl("/messages/123")
}
}
----
======
What if we want to test the form view that lets us create the message? For example,
assume our form looks like the following snippet:
[source,xml,indent=0]
----
<form id="messageForm" action="/messages/" method="post">
<div class="pull-right"><a href="/messages/">Messages</a></div>
<label for="summary">Summary</label>
<input type="text" class="required" id="summary" name="summary" value="" />
<label for="text">Message</label>
<textarea id="text" name="text"></textarea>
<div class="form-actions">
<input type="submit" value="Create" />
</div>
</form>
----
How do we ensure that our form produce the correct request to create a new message? A
naive attempt might resemble the following:
[tabs]
======
Java::
+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
mockMvc.perform(get("/messages/form"))
.andExpect(xpath("//input[@name='summary']").exists())
.andExpect(xpath("//textarea[@name='text']").exists());
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
mockMvc.get("/messages/form").andExpect {
xpath("//input[@name='summary']") { exists() }
xpath("//textarea[@name='text']") { exists() }
}
----
======
This test has some obvious drawbacks. If we update our controller to use the parameter
`message` instead of `text`, our form test continues to pass, even though the HTML form
is out of sync with the controller. To resolve this we can combine our two tests, as
follows:
[tabs]
======
Java::
+
[[mockmvc-server-htmlunit-mock-mvc-test]]
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
----
String summaryParamName = "summary";
String textParamName = "text";
mockMvc.perform(get("/messages/form"))
.andExpect(xpath("//input[@name='" + summaryParamName + "']").exists())
.andExpect(xpath("//textarea[@name='" + textParamName + "']").exists());
MockHttpServletRequestBuilder createMessage = post("/messages/")
.param(summaryParamName, "Spring Rocks")
.param(textParamName, "In case you didn't know, Spring Rocks!");
mockMvc.perform(createMessage)
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/messages/123"));
----
Kotlin::
+
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
----
val summaryParamName = "summary";
val textParamName = "text";
mockMvc.get("/messages/form").andExpect {
xpath("//input[@name='$summaryParamName']") { exists() }
xpath("//textarea[@name='$textParamName']") { exists() }
}
mockMvc.post("/messages/") {
param(summaryParamName, "Spring Rocks")
param(textParamName, "In case you didn't know, Spring Rocks!")
}.andExpect {
status().is3xxRedirection()
redirectedUrl("/messages/123")
}
----
======
This would reduce the risk of our test incorrectly passing, but there are still some
problems:
* What if we have multiple forms on our page? Admittedly, we could update our XPath
expressions, but they get more complicated as we take more factors into account: Are
the fields the correct type? Are the fields enabled? And so on.
* Another issue is that we are doing double the work we would expect. We must first
verify the view, and then we submit the view with the same parameters we just verified.
Ideally, this could be done all at once.
* Finally, we still cannot account for some things. For example, what if the form has
JavaScript validation that we wish to test as well?
The overall problem is that testing a web page does not involve a single interaction.
Instead, it is a combination of how the user interacts with a web page and how that web
page interacts with other resources. For example, the result of a form view is used as
the input to a user for creating a message. In addition, our form view can potentially
use additional resources that impact the behavior of the page, such as JavaScript
validation.
[[mockmvc-server-htmlunit-why-integration]]
== Integration Testing to the Rescue?
To resolve the issues mentioned earlier, we could perform end-to-end integration testing,
but this has some drawbacks. Consider testing the view that lets us page through the
messages. We might need the following tests:
* Does our page display a notification to the user to indicate that no results are
available when the messages are empty?
* Does our page properly display a single message?
* Does our page properly support paging?
To set up these tests, we need to ensure our database contains the proper messages. This
leads to a number of additional challenges:
* Ensuring the proper messages are in the database can be tedious. (Consider foreign key
constraints.)
* Testing can become slow, since each test would need to ensure that the database is in
the correct state.
* Since our database needs to be in a specific state, we cannot run tests in parallel.
* Performing assertions on such items as auto-generated IDs, timestamps, and others can
be difficult.
These challenges do not mean that we should abandon end-to-end integration testing
altogether. Instead, we can reduce the number of end-to-end integration tests by
refactoring our detailed tests to use mock services that run much faster, more reliably,
and without side effects. We can then implement a small number of true end-to-end
integration tests that validate simple workflows to ensure that everything works together
properly.
[[mockmvc-server-htmlunit-why-mockmvc]]
== Enter HtmlUnit Integration
So how can we achieve a balance between testing the interactions of our pages and still
retain good performance within our test suite? The answer is: "`By integrating MockMvc
with HtmlUnit.`"
[[mockmvc-server-htmlunit-options]]
== HtmlUnit Integration Options
You have a number of options when you want to integrate MockMvc with HtmlUnit:
* xref:testing/mockmvc/htmlunit/mah.adoc[MockMvc and HtmlUnit]: Use this option if you
want to use the raw HtmlUnit libraries.
* xref:testing/mockmvc/htmlunit/webdriver.adoc[MockMvc and WebDriver]: Use this option to
ease development and reuse code between integration and end-to-end testing.
* xref:testing/mockmvc/htmlunit/geb.adoc[MockMvc and Geb]: Use this option if you want to
use Groovy for testing, ease development, and reuse code between integration and
end-to-end testing.

View File

@@ -0,0 +1,24 @@
[[mockmvc-overview]]
= Overview
:page-section-summary-toc: 1
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.
`MockMvc` aims to provide more complete testing for Spring MVC controllers without a
running server. It does that by invoking the `DispatcherServlet` and passing
xref:testing/unit.adoc#mock-objects-servlet["`mock`" implementations of the Servlet API] from the
`spring-test` module which replicates the full Spring MVC request handling without
a running server.
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 using Hamcrest, or through
`MockMvcTester` that provides a fluent API using AssertJ. Finally, you can also use it
through the xref:testing/webtestclient.adoc[WebTestClient] API with MockMvc plugged in
as the server to handle requests with.

View File

@@ -0,0 +1,11 @@
[[mockmvc-server-resources]]
= Further Examples
:page-section-summary-toc: 1
The framework's own tests include
{spring-framework-code}/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
{spring-framework-code}/spring-test/src/test/java/org/springframework/test/web/servlet/samples/client[
WebTestClient]. Browse these examples for further ideas.

View File

@@ -0,0 +1,29 @@
[[mockmvc-server-setup-options]]
= Setup Options
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.
Which setup option should you use?
The use of an `ApplicationContext` loads your actual Spring MVC configuration, resulting
The `WebApplicationContext`-based test loads your actual Spring MVC configuration,
resulting in a more complete integration test. Since the TestContext framework caches
the loaded Spring configuration, it helps keep tests running fast, even as you introduce
more tests in your test suite using the same configuration. Furthermore, you can
override services used by your controller using `@MockitoBean` to remain focused on
testing the web layer.
The standalone test, on the other hand, is a little closer to a unit test. It tests one
controller at a time. You can manually inject the controller with mock dependencies, and
it does not involve loading Spring configuration. Such tests are more focused on style
and make it easier to see which controller is being tested, whether any specific Spring
MVC configuration is required to work, and so on. The standalone setup is also a very
convenient way to write ad-hoc tests to verify specific behavior or to debug an issue.
As with most "`integration versus unit testing`" debates, there is no right or wrong
answer. However, using standalone tests does imply the need for additional integration
tests to verify your Spring MVC configuration. Alternatively, you can write all your
tests with a `WebApplicationContext`, so that they always test against your actual Spring
MVC configuration.

View File

@@ -0,0 +1,46 @@
[[mockmvc-vs-end-to-end-integration-tests]]
= MockMvc vs End-to-End Tests
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.
The easiest way to think about this is by starting with a blank `MockHttpServletRequest`.
Whatever you add to it is what the request becomes. Things that may catch you by surprise
are that there is no context path by default; no `jsessionid` cookie; no forwarding,
error, or async dispatches; and, therefore, no actual JSP rendering. Instead,
"`forwarded`" and "`redirected`" URLs are saved in the `MockHttpServletResponse` and can
be asserted with expectations.
This means that, if you use JSPs, you can verify the JSP page to which the request was
forwarded, but no HTML is rendered. In other words, the JSP is not invoked. Note,
however, that all other rendering technologies that do not rely on forwarding, such as
Thymeleaf and Freemarker, render HTML to the response body as expected. The same is true
for rendering JSON, XML, and other formats through `@ResponseBody` methods.
Alternatively, you may consider the full end-to-end integration testing support from
Spring Boot with `@SpringBootTest`. See the
{spring-boot-docs}/spring-boot-features.html#boot-features-testing[Spring Boot Reference Guide].
There are pros and cons for each approach. The options provided in Spring MVC Test are
different stops on the scale from classic unit testing to full integration testing. To be
certain, none of the options in Spring MVC Test fall under the category of classic unit
testing, but they are a little closer to it. For example, you can isolate the web layer
by injecting mocked services into controllers, in which case you are testing the web
layer only through the `DispatcherServlet` but with actual Spring configuration, as you
might test the data access layer in isolation from the layers above it. Also, you can use
the stand-alone setup, focusing on one controller at a time and manually providing the
configuration required to make it work.
Another important distinction when using Spring MVC Test is that, conceptually, such
tests are the server-side, so you can check what handler was used, if an exception was
handled with a HandlerExceptionResolver, what the content of the model is, what binding
errors there were, and other details. That means that it is easier to write expectations,
since the server is not an opaque box, as it is when testing it through an actual HTTP
client. This is generally an advantage of classic unit testing: It is easier to write,
reason about, and debug but does not replace the need for full integration tests. At the
same time, it is important not to lose sight of the fact that the response is the most
important thing to check. In short, there is room here for multiple styles and strategies
of testing even within the same project.