Polish HtmlUnit support in the reference manual
Issues SPR-13158
This commit is contained in:
@@ -4158,31 +4158,30 @@ coverage based on Spring MVC Test.
|
||||
==== HtmlUnit Integration
|
||||
|
||||
Spring provides integration between <<spring-mvc-test-server,MockMvc>> and
|
||||
http://htmlunit.sourceforge.net/[HtmlUnit]. This simplifies performing end to end testing
|
||||
http://htmlunit.sourceforge.net/[HtmlUnit]. This simplifies performing end-to-end testing
|
||||
when using HTML based views. This integration enables developers to:
|
||||
|
||||
* Easily test pages using tools (i.e. http://htmlunit.sourceforge.net/[HtmlUnit],
|
||||
* Easily test HTML pages using tools such as http://htmlunit.sourceforge.net/[HtmlUnit],
|
||||
http://seleniumhq.org/projects/webdriver/[WebDriver], &
|
||||
http://www.gebish.org/manual/current/testing.html#spock_junit__testng[Geb]) that we
|
||||
already use for integration testing without starting an application server
|
||||
* Support testing of JavaScript
|
||||
* Optionally test using mock services to speed up testing.
|
||||
* Share logic between end-to-end tests and integration tests
|
||||
|
||||
http://www.gebish.org/manual/current/testing.html#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 will work with templating technologies that do not rely on a Servlet Container
|
||||
(i.e. Thymeleaf, Freemarker, Velocity, etc). It does not work with JSPs since they rely on
|
||||
`MockMvc` works with templating technologies that do not rely on a Servlet Container (e.g.,
|
||||
Thymeleaf, Freemarker, Velocity, etc.), but it does not work with JSPs since they rely on
|
||||
the Servlet Container.
|
||||
====
|
||||
|
||||
[[spring-mvc-test-server-htmlunit-why]]
|
||||
===== Why HtmlUnit Integration?
|
||||
|
||||
The most obvious question that comes to mind is "Why do I need this?" The answer is best
|
||||
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 allows CRUD operations on a `Message` object. The application also allows
|
||||
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`.
|
||||
@@ -4228,9 +4227,9 @@ mockMvc.perform(get("/messages/form"))
|
||||
.andExpect(xpath("//textarea[@name='text']").exists());
|
||||
----
|
||||
|
||||
This test has some obvious problems. If we updated our controller to use the parameter
|
||||
"message" instead of "text", our test would would incorrectly pass. To resolve this we
|
||||
could combine our two tests:
|
||||
This test has some obvious drawbacks. If we update our controller to use the parameter
|
||||
`message` instead of `text`, our form test would continue to pass even though the HTML
|
||||
form is out of synch with the controller. To resolve this we can combine our two tests.
|
||||
|
||||
[[spring-mvc-test-server-htmlunit-mock-mvc-test]]
|
||||
[source,java]
|
||||
@@ -4251,75 +4250,72 @@ mockMvc.perform(createMessage)
|
||||
----
|
||||
|
||||
This would reduce the risk of our test incorrectly passing, but there are still some
|
||||
problems:
|
||||
problems.
|
||||
|
||||
* What if we had multiple forms on our page? Admittedly we could update our xpath
|
||||
expressions, but they get more complicated the more factors we take into account (are the
|
||||
fields the correct type, are the fields enabled, etc).
|
||||
* What if we have multiple forms on our page? Admittedly we could update our xpath
|
||||
expressions, but they get more complicated the more factors we take into account (Are the
|
||||
fields the correct type? Are the fields enabled? etc.).
|
||||
* 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.
|
||||
* Last, there are some things that we still cannot account for. For example, what if the
|
||||
form has JavaScript validation that we wish to validate too?
|
||||
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, there are some things that we still cannot account for. 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 is not 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 form view is used as an input to a user
|
||||
for creating a message. Another example is that our form view utilizes additional
|
||||
resources, like JavaScript validation, that impact the behavior of the page.
|
||||
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 may potentially
|
||||
utilize additional resources which impact the behavior of the page, such as JavaScript
|
||||
validation.
|
||||
|
||||
[[spring-mvc-test-server-htmlunit-why-integration]]
|
||||
====== Integration testing to the rescue?
|
||||
|
||||
To resolve the issues above we could perform integration testing, but this has some
|
||||
obvious drawbacks. Consider testing the view that allows us to page through the messages.
|
||||
We might need the following tests:
|
||||
To resolve the issues above we could perform end-to-end integration testing, but this has
|
||||
some obvious drawbacks. Consider testing the view that allows us to page through the messages.
|
||||
We might need the following tests.
|
||||
|
||||
* Does our page display a message to the user indicating that no results are available
|
||||
* Does our page display a notification to the user indicating 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 these tests up we would need to ensure our database contained the proper messages
|
||||
in it. This leads to a number of problems:
|
||||
To set up these tests, we would need to ensure our database contained the proper messages
|
||||
in it. This leads to a number of additional challenges.
|
||||
|
||||
* Ensuring the proper messages are in the database can be tedious (think possible foreign
|
||||
keys).
|
||||
* Testing would be slow since each test would require ensuring the database was in the
|
||||
correct state.
|
||||
* Since our database needs to be in a specific state, we cannot run the test in parallel.
|
||||
* Assertions on things like auto generated ids, timestamps, etc can be challenging.
|
||||
* 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 things like auto-generated ids, timestamps, etc. can be difficult.
|
||||
|
||||
These problems do not mean that we should abandon integration testing all together.
|
||||
Instead, we can reduce the number of integration tests by moving our detailed tests to use
|
||||
mock services which will perform much faster. We can then use fewer integration tests that
|
||||
validate simple workflows to ensure that everything works together properly.
|
||||
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 which will execute 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.
|
||||
|
||||
[[spring-mvc-test-server-htmlunit-why-mockmvc]]
|
||||
====== Enter HtmlUnit Integration
|
||||
|
||||
So how can we provide a balance between testing the interactions of our pages and still
|
||||
get performance? I'm sure you already guessed it...integrating with HtmlUnit
|
||||
will allow us to:
|
||||
|
||||
* Easily test our pages using tools (i.e. HtmlUnit, WebDriver, & Geb) that we already use
|
||||
for integration testing without starting an application server
|
||||
* Support testing of JavaScript
|
||||
* Optionally test using mock services to speed up testing.
|
||||
* Share logic between end-to-end tests and integration tests
|
||||
|
||||
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."
|
||||
|
||||
[[spring-mvc-test-server-htmlunit-options]]
|
||||
====== HtmlUnit Integration Options
|
||||
|
||||
There are a number of ways to integrate with HtmlUnit. You can find a summary below:
|
||||
There are a number of ways to integrate `MockMvc` with HtmlUnit.
|
||||
|
||||
* <<spring-mvc-test-server-htmlunit-mah,MockMvc and HtmlUnit>> - Use this option if you want the raw libraries
|
||||
* <<spring-mvc-test-server-htmlunit-webdriver,MockMvc and WebDriver>> - Use this option to ease development and be able to reuse code
|
||||
between integration and end-to-end testing.
|
||||
* <<spring-mvc-test-server-htmlunit-geb,MockMvc and Geb>> - Use this option if you like using Groovy for testing, would like to
|
||||
ease development, and be able to reuse code between integration and end-to-end testing.
|
||||
* <<spring-mvc-test-server-htmlunit-mah,MockMvc and HtmlUnit>>: Use this option if you
|
||||
want to use the raw HtmlUnit libraries.
|
||||
* <<spring-mvc-test-server-htmlunit-webdriver,MockMvc and WebDriver>>: Use this option to
|
||||
ease development and reuse code between integration and end-to-end testing.
|
||||
* <<spring-mvc-test-server-htmlunit-geb,MockMvc and Geb>>: Use this option if you would
|
||||
like to use Groovy for testing, ease development, and reuse code between integration and
|
||||
end-to-end testing.
|
||||
|
||||
[[spring-mvc-test-server-htmlunit-mah]]
|
||||
===== MockMvc and HtmlUnit
|
||||
@@ -4331,7 +4327,7 @@ want to use the raw HtmlUnit libraries.
|
||||
====== MockMvc and HtmlUnit Setup
|
||||
|
||||
We can easily create an HtmlUnit `WebClient` that integrates with `MockMvc` using the
|
||||
following:
|
||||
`MockMvcWebClientBuilder` as follows.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@@ -4354,15 +4350,17 @@ This is a simple example of using `MockMvcWebClientBuilder`. For advanced usage
|
||||
<<Advanced MockMvcWebClientBuilder>>
|
||||
====
|
||||
|
||||
This will ensure any URL that has a host of "localhost" will be directed at our MockMvc
|
||||
instance without the need for HTTP. Any other URL will be requested as normal. This allows
|
||||
for easily testing with the use of CDNs.
|
||||
This will ensure that any URL referencing `localhost` as the server will be directed to
|
||||
our `MockMvc` instance without the need for a real HTTP connection. Any other URL will be
|
||||
requested using a network connection as normal. This allows us to easily test the use of
|
||||
CDNs.
|
||||
|
||||
[[spring-mvc-test-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. For example, we can request the view to create a message with the following:
|
||||
application to a Servlet container. For example, we can request the view to create
|
||||
a message with the following.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@@ -4371,11 +4369,12 @@ HtmlPage createMsgFormPage = webClient.getPage("http://localhost/messages/form")
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
The default context path is `""`. Alternatively, we could have specified the context
|
||||
path as illustrated in <<Advanced MockMvcWebClientBuilder>>.
|
||||
The default context path is `""`. Alternatively, we can specify the context path as
|
||||
illustrated in <<Advanced MockMvcWebClientBuilder>>.
|
||||
====
|
||||
|
||||
We can then fill out the form and submit it to create a message.
|
||||
Once we have a reference to the `HtmlPage`, we can then fill out the form and submit
|
||||
it to create a message.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@@ -4388,7 +4387,8 @@ HtmlSubmitInput submit = form.getOneHtmlElementByAttribute("input", "type", "sub
|
||||
HtmlPage newMessagePage = submit.click();
|
||||
----
|
||||
|
||||
Finally, we can verify that a new message was created successfully
|
||||
Finally, we can verify that a new message was created successfully. The following
|
||||
assertions use the https://code.google.com/p/fest/[FEST assertion library].
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@@ -4404,12 +4404,11 @@ assertThat(text).isEqualTo("In case you didn't know, Spring Rocks!");
|
||||
This improves on our <<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. This reduces the overhead significantly.
|
||||
it, thereby significantly reducing the overhead.
|
||||
|
||||
Another important factor is that
|
||||
http://htmlunit.sourceforge.net/javascript.html[HtmlUnit uses Mozilla Rhino engine] to
|
||||
evaluate JavaScript on your pages. This means, that we can verify our JavaScript methods
|
||||
as well!
|
||||
Another important factor is that http://htmlunit.sourceforge.net/javascript.html[HtmlUnit
|
||||
uses the Mozilla Rhino engine] to evaluate JavaScript. This means that we can test the
|
||||
behavior of JavaScript within our pages as well!
|
||||
|
||||
Refer to the http://htmlunit.sourceforge.net/gettingStarted.html[HtmlUnit documentation]
|
||||
for additional information about using HtmlUnit.
|
||||
@@ -4417,7 +4416,9 @@ for additional information about using HtmlUnit.
|
||||
[[spring-mvc-test-server-htmlunit-mah-advanced-builder]]
|
||||
====== Advanced MockMvcWebClientBuilder
|
||||
|
||||
In our example above we used `MockMvcWebClientBuilder` in the simplest way possible.
|
||||
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 here.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@@ -4434,10 +4435,12 @@ public void setup() {
|
||||
}
|
||||
----
|
||||
|
||||
We could also specify some optional arguments:
|
||||
We can also specify additional configuration options.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
WebClient webClient;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
webClient = MockMvcWebClientBuilder
|
||||
@@ -4446,13 +4449,14 @@ public void setup() {
|
||||
// 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 too
|
||||
// the following will use MockMvc for example.com and example.org as well
|
||||
.useMockMvcForHosts("example.com","example.org")
|
||||
.build();
|
||||
}
|
||||
----
|
||||
|
||||
We could also perform the exact same setup using the following:
|
||||
As an alternative, we can perform the exact same setup by configuring the `MockMvc`
|
||||
instance separately and supplying it to the `MockMvcWebClientBuilder` as follows.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@@ -4466,7 +4470,7 @@ webClient = MockMvcWebClientBuilder
|
||||
// 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 too
|
||||
// the following will use MockMvc for example.com and example.org as well
|
||||
.useMockMvcForHosts("example.com","example.org")
|
||||
.build();
|
||||
----
|
||||
@@ -4483,16 +4487,16 @@ For additional information on creating a `MockMvc` instance refer to
|
||||
[[spring-mvc-test-server-htmlunit-webdriver]]
|
||||
===== MockMvc and WebDriver
|
||||
|
||||
In the previous section, we have already seen how to use MockMvc with HtmlUnit.
|
||||
In this section, we will leverage additional abstractions within
|
||||
In the previous sections, we have seen how to use `MockMvc` in conjunction with the raw
|
||||
HtmlUnit APIs. In this section, we will leverage additional abstractions within the Selenium
|
||||
http://docs.seleniumhq.org/projects/webdriver/[WebDriver] to make things even easier.
|
||||
|
||||
[[spring-mvc-test-server-htmlunit-webdriver-why]]
|
||||
====== Why WebDriver and MockMvc?
|
||||
|
||||
We can already use HtmlUnit and MockMvc, so why would we want to use WebDriver? WebDriver
|
||||
provides a very elegant API and allows us to easily organize our code. To better
|
||||
understand, let's explore an example.
|
||||
We can already use HtmlUnit and `MockMvc`, so why would we want to use `WebDriver`? The
|
||||
Selenium `WebDriver` provides a very elegant API that allows us to easily organize our code.
|
||||
To better understand, let's explore an example.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
@@ -4501,14 +4505,15 @@ a Selenium Server to run your tests.
|
||||
====
|
||||
|
||||
Suppose we need to ensure that a message is created properly. The tests involve finding
|
||||
the html inputs, filling them out, and making various assertions.
|
||||
the HTML form input elements, filling them out, and making various assertions.
|
||||
|
||||
There are many tests because we want to test error conditions as well. For example, we
|
||||
want to ensure that if we fill out only part of the form we get an error. If we fill out
|
||||
the entire form, the newly created message is displayed afterwards.
|
||||
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 was named "summary", then we might have something like the following
|
||||
repeated everywhere within our tests:
|
||||
If one of the fields were named "summary", then we might have something like the
|
||||
following repeated in multiple places within our tests.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@@ -4516,15 +4521,15 @@ HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary");
|
||||
summaryInput.setValueAttribute(summary);
|
||||
----
|
||||
|
||||
So what happens if we change the id to be "smmry".
|
||||
This means we would have to update all of our tests! Instead we would hope that we wrote a
|
||||
bit more elegant code where filling out the form was in its own method:
|
||||
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! Of course, this violates the _DRY Principle_; so
|
||||
we should ideally extract this code into its own method as follows.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public HtmlPage createMessage(HtmlPage currentPage, String summary, String text) {
|
||||
setSummary(currentPage, summary);
|
||||
...
|
||||
// ...
|
||||
}
|
||||
|
||||
public void setSummary(HtmlPage currentPage, String summary) {
|
||||
@@ -4533,19 +4538,20 @@ public void setSummary(HtmlPage currentPage, String summary) {
|
||||
}
|
||||
----
|
||||
|
||||
This ensures that if we change the UI we do not have to update all of our tests.
|
||||
This ensures that we do not have to update all of our tests if we change the UI.
|
||||
|
||||
We might take it a step further and place this logic within an Object that represents the
|
||||
`HtmlPage` we are currently on.
|
||||
We might even take this a step further and place this logic within an Object that
|
||||
represents the `HtmlPage` we are currently on.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public class CreateMessagePage {
|
||||
HtmlPage currentPage;
|
||||
|
||||
HtmlTextInput summaryInput;
|
||||
final HtmlPage currentPage;
|
||||
|
||||
HtmlSubmitInput submit;
|
||||
final HtmlTextInput summaryInput;
|
||||
|
||||
final HtmlSubmitInput submit;
|
||||
|
||||
public CreateMessagePage(HtmlPage currentPage) {
|
||||
this.currentPage = currentPage;
|
||||
@@ -4575,13 +4581,13 @@ public class CreateMessagePage {
|
||||
Formerly, this pattern is known as the
|
||||
https://code.google.com/p/selenium/wiki/PageObjects[Page Object Pattern]. While we can
|
||||
certainly do this with HtmlUnit, WebDriver provides some tools that we will explore in the
|
||||
following sections make this pattern much easier.
|
||||
following sections to make this pattern much easier to implement.
|
||||
|
||||
[[spring-mvc-test-server-htmlunit-webdriver-setup]]
|
||||
====== MockMvc and WebDriver Setup
|
||||
|
||||
We can easily create a WebDriver implementation that integrates with MockMvc using the
|
||||
following:
|
||||
We can easily create a Selenium `WebDriver` that integrates with `MockMvc` using the
|
||||
`MockMvcHtmlUnitDriverBuilder` as follows.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@@ -4604,15 +4610,17 @@ This is a simple example of using `MockMvcHtmlUnitDriverBuilder`.
|
||||
For more advanced usage, refer to <<Advanced MockMvcHtmlUnitDriverBuilder>>
|
||||
====
|
||||
|
||||
This will ensure any URL that has a host of "localhost" will be directed at our MockMvc
|
||||
instance without the need for HTTP. Any other URL will be requested as normal. This allows
|
||||
for easily testing with the use of CDNs.
|
||||
This will ensure that any URL referencing `localhost` as the server will be directed to
|
||||
our `MockMvc` instance without the need for a real HTTP connection. Any other URL will be
|
||||
requested using a network connection as normal. This allows us to easily test the use of
|
||||
CDNs.
|
||||
|
||||
[[spring-mvc-test-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. For example, we can request the view to create a message with the following:
|
||||
application to a Servlet container. For example, we can request the view to create
|
||||
a message with the following.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@@ -4628,10 +4636,10 @@ ViewMessagePage viewMessagePage =
|
||||
----
|
||||
|
||||
This improves on the design of our
|
||||
<<spring-mvc-test-server-htmlunit-mah-usage,HtmlUnit test>> by leveraging the Page Object
|
||||
Pattern. As we mentioned in <<spring-mvc-test-server-htmlunit-webdriver-why>>, we could
|
||||
use the Page Object Pattern with HtmlUnit, but it is much easier now. Let's take a look at
|
||||
our `CreateMessagePage`.
|
||||
<<spring-mvc-test-server-htmlunit-mah-usage,HtmlUnit test>> by leveraging the _Page Object
|
||||
Pattern_. As we mentioned in <<spring-mvc-test-server-htmlunit-webdriver-why>>, we can
|
||||
use the Page Object Pattern with HtmlUnit, but it is much easier with WebDriver. Let's
|
||||
take a look at our new `CreateMessagePage` implementation.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@@ -4664,27 +4672,28 @@ public class CreateMessagePage
|
||||
}
|
||||
----
|
||||
|
||||
<1> The first thing you will notice is that our `CreateMessagePage` extends the
|
||||
`AbstractPage`. We won't go over the details of `AbstractPage`, but in summary it contains
|
||||
all the common functionality of all our pages. For example, if your application has a
|
||||
navigational bar, global error messages, etc. This logic can be placed in a shared
|
||||
<1> The first thing you will notice is that `CreateMessagePage` extends the
|
||||
`AbstractPage`. We won't 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, etc., this logic can be placed in a shared
|
||||
location.
|
||||
|
||||
<2> The next thing you will find is that we have a member variable for each of the parts
|
||||
of the HTML, `WebElement`, we are interested in. ``WebDriver``'s
|
||||
https://code.google.com/p/selenium/wiki/PageFactory[PageFactory] allows us to remove a lot
|
||||
of code from HtmlUnit version of `CreateMessagePage` by automatically resolving each
|
||||
`WebElement`. The
|
||||
<2> The next thing you will notice is that we have a member variable for each of the
|
||||
parts of the HTML page that we are interested in. These are of type `WebElement`.
|
||||
``WebDriver``'s https://code.google.com/p/selenium/wiki/PageFactory[PageFactory] allows
|
||||
us to remove a lot of code from the HtmlUnit version of `CreateMessagePage` by
|
||||
automatically resolving each `WebElement`. The
|
||||
http://selenium.googlecode.com/git/docs/api/java/org/openqa/selenium/support/PageFactory.html#initElements-org.openqa.selenium.WebDriver-java.lang.Class-[PageFactory#initElements(WebDriver,Class<T>)]
|
||||
method will automatically resolve each `WebElement` by using the field name and trying to
|
||||
look it up by id or name of the element on the HTML page.
|
||||
method will automatically resolve 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://code.google.com/p/selenium/wiki/PageFactory#Making_the_Example_Work_Using_Annotations[@FindBy annotation]
|
||||
to override the default. Our example demonstrates how we can use the `@FindBy` annotation
|
||||
to lookup our submit button using the css selector of *input[type=submit]*.
|
||||
to override the default lookup behavior. Our example demonstrates how to use the `@FindBy`
|
||||
annotation to look up our submit button using a css selector, *input[type=submit]*.
|
||||
|
||||
Finally, we can verify that a new message was created successfully
|
||||
Finally, we can verify that a new message was created successfully. The following
|
||||
assertions use the https://code.google.com/p/fest/[FEST assertion library].
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@@ -4692,7 +4701,7 @@ assertThat(viewMessagePage.getMessage()).isEqualTo(expectedMessage);
|
||||
assertThat(viewMessagePage.getSuccess()).isEqualTo("Successfully created a new message");
|
||||
----
|
||||
|
||||
We can see that our `ViewMessagePage` can allow us to interact with our custom domain
|
||||
We can see that our `ViewMessagePage` allows us to interact with our custom domain
|
||||
model. For example, it exposes a method that returns a `Message` object.
|
||||
|
||||
[source,java]
|
||||
@@ -4709,7 +4718,7 @@ public Message getMessage() throws ParseException {
|
||||
|
||||
We can then leverage the rich domain objects in our assertions.
|
||||
|
||||
Last, don't forget to close the `WebDriver` instance when we are done.
|
||||
Lastly, don't forget to _close_ the `WebDriver` instance when the test is complete.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@@ -4721,13 +4730,15 @@ public void destroy() {
|
||||
}
|
||||
----
|
||||
|
||||
For additional information on using WebDriver, refer to the
|
||||
For additional information on using WebDriver, refer to the Selenium
|
||||
https://code.google.com/p/selenium/wiki/GettingStarted[WebDriver documentation].
|
||||
|
||||
[[spring-mvc-test-server-htmlunit-webdriver-advanced-builder]]
|
||||
====== Advanced MockMvcHtmlUnitDriverBuilder
|
||||
|
||||
In our example above we used `MockMvcHtmlUnitDriverBuilder` in the simplest way possible.
|
||||
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.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@@ -4744,7 +4755,7 @@ public void setup() {
|
||||
}
|
||||
----
|
||||
|
||||
We could also specify some optional arguments:
|
||||
We can also specify additional configuration options.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@@ -4758,13 +4769,14 @@ public void setup() {
|
||||
// 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 too
|
||||
// the following will use MockMvc for example.com and example.org as well
|
||||
.useMockMvcForHosts("example.com","example.org")
|
||||
.build();
|
||||
}
|
||||
----
|
||||
|
||||
We could also perform the exact same setup using the following:
|
||||
As an alternative, we can perform the exact same setup by configuring the `MockMvc`
|
||||
instance separately and supplying it to the `MockMvcHtmlUnitDriverBuilder` as follows.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@@ -4778,7 +4790,7 @@ driver = MockMvcHtmlUnitDriverBuilder
|
||||
// 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 too
|
||||
// the following will use MockMvc for example.com and example.org as well
|
||||
.useMockMvcForHosts("example.com","example.org")
|
||||
.build();
|
||||
----
|
||||
@@ -4795,29 +4807,29 @@ For additional information on creating a `MockMvc` instance refer to
|
||||
[[spring-mvc-test-server-htmlunit-geb]]
|
||||
===== MockMvc and Geb
|
||||
|
||||
In the previous section, we saw how to use MockMvc with WebDriver.
|
||||
In this section, we will use http://www.gebish.org/[Geb] to make our tests more Groovy.
|
||||
In the previous section, we saw how to use `MockMvc` with `WebDriver`. In this section,
|
||||
we will use http://www.gebish.org/[Geb] to make our tests even Groovy-er.
|
||||
|
||||
|
||||
[[spring-mvc-test-server-htmlunit-geb-why]]
|
||||
====== Why Geb and MockMvc?
|
||||
|
||||
Geb is backed by WebDriver, so it offers many of the
|
||||
<<spring-mvc-test-server-htmlunit-webdriver-why,same benefits>> we got from WebDriver.
|
||||
However, Geb makes things even easier by taking care of some of the boiler plate code for
|
||||
us.
|
||||
<<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.
|
||||
|
||||
[[spring-mvc-test-server-htmlunit-geb-setup]]
|
||||
====== MockMvc and Geb Setup
|
||||
|
||||
We can easily initialize Geb with a WebDriver implementation that uses `MockMvc` with the
|
||||
following:
|
||||
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, springSecurity())
|
||||
.webAppContextSetup(context)
|
||||
.build()
|
||||
}
|
||||
----
|
||||
@@ -4828,15 +4840,17 @@ This is a simple example of using `MockMvcHtmlUnitDriverBuilder`.
|
||||
For more advanced usage, refer to <<Advanced MockMvcHtmlUnitDriverBuilder>>
|
||||
====
|
||||
|
||||
This will ensure any URL that has a host of "localhost" will be directed at our MockMvc
|
||||
instance without the need for HTTP. Any other URL will be requested as normal. This allows
|
||||
for easily testing with the use of CDNs.
|
||||
This will ensure that any URL referencing `localhost` as the server will be directed to
|
||||
our `MockMvc` instance without the need for a real HTTP connection. Any other URL will be
|
||||
requested using a network connection as normal. This allows us to easily test the use of
|
||||
CDNs.
|
||||
|
||||
[[spring-mvc-test-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.
|
||||
For example, we can request the view to create a message with the following:
|
||||
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]
|
||||
----
|
||||
@@ -4857,19 +4871,17 @@ Any unrecognized method calls or property accesses/references that are not found
|
||||
forwarded to the current page object. This removes a lot of the boilerplate code we needed
|
||||
when using WebDriver directly.
|
||||
|
||||
Additionally, this improves on the design of our
|
||||
<<spring-mvc-test-server-htmlunit-mah-usage,HtmlUnit test>>. The most obvious change is
|
||||
that we are now using the Page Object Pattern. As we mentioned in
|
||||
<<spring-mvc-test-server-htmlunit-webdriver-why>>, we could use the Page Object Pattern
|
||||
with HtmlUnit, but it is much easier now.
|
||||
|
||||
Let's take a look at our `CreateMessagePage`.
|
||||
As with direct WebDriver usage, this improves on the design of our
|
||||
<<spring-mvc-test-server-htmlunit-mah-usage,HtmlUnit test>> by leveraging 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. Let's take a look at our new Groovy-based
|
||||
`CreateMessagePage` implementation.
|
||||
|
||||
[source,groovy]
|
||||
----
|
||||
class CreateMessagePage extends Page {
|
||||
static at = { assert title == 'Messages : Create'; true }
|
||||
static url = 'messages/form'
|
||||
static at = { assert title == 'Messages : Create'; true }
|
||||
static content = {
|
||||
submit { $('input[type=submit]') }
|
||||
form { $('form') }
|
||||
@@ -4878,20 +4890,19 @@ class CreateMessagePage extends Page {
|
||||
}
|
||||
----
|
||||
|
||||
The first thing you will notice is that our `CreateMessagePage` extends the `Page`.
|
||||
We won't go over the details of `Page`, but in summary it contains base functionality for all our pages.
|
||||
|
||||
The next thing you will notice is that we define a URL in which this page can be found.
|
||||
This allows us to navigate to the page with:
|
||||
The first thing you will notice is that our `CreateMessagePage` extends `Page`. We won't
|
||||
go over the details of `Page`, but in summary it contains common functionality for all of
|
||||
our pages. The next thing you will notice is that we define a URL in which this page can
|
||||
be found. This allows us to navigate to the page as follows.
|
||||
|
||||
[source,groovy]
|
||||
----
|
||||
to CreateMessagePage
|
||||
----
|
||||
|
||||
We also have a 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 with:
|
||||
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]
|
||||
----
|
||||
@@ -4902,16 +4913,16 @@ errors.contains('This field is required.')
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
We use an assertion in the closure, so we can determine where things went wrong if we were
|
||||
at the wrong page.
|
||||
We use an assertion in the closure, so that we can determine where things went wrong if
|
||||
we were at the wrong page.
|
||||
====
|
||||
|
||||
We last create a content closure that specifies all the areas of interest within the page.
|
||||
Next we create a `content` closure that specifies all the areas of interest within the page.
|
||||
We can use a
|
||||
http://www.gebish.org/manual/current/intro.html#the_jquery_ish_navigator_api[jQuery-ish Navigator API]
|
||||
to select the content we are interested in.
|
||||
|
||||
Finally, we can verify that a new message was created successfully
|
||||
Finally, we can verify that a new message was created successfully.
|
||||
|
||||
[source,groovy]
|
||||
----
|
||||
@@ -4924,6 +4935,9 @@ summary == expectedSummary
|
||||
message == expectedMessage
|
||||
----
|
||||
|
||||
For further details on how to get the most out of Geb, consult
|
||||
http://www.gebish.org/manual/current/[The Book of Geb] user's manual.
|
||||
|
||||
|
||||
[[spring-mvc-test-client]]
|
||||
==== Client-Side REST Tests
|
||||
|
||||
Reference in New Issue
Block a user