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:
@@ -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.
|
||||
@@ -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`].
|
||||
@@ -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`.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
|
||||
======
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
----
|
||||
======
|
||||
|
||||
@@ -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`.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
@@ -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].
|
||||
|
||||
@@ -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].
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user