From 8de7848ab3a2617667e29fd7065249117ebf28d8 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Tue, 28 Jul 2015 18:46:18 +0200 Subject: [PATCH] Polish HtmlUnit support in the reference manual Issues SPR-13158 --- src/asciidoc/testing.adoc | 348 ++++++++++++++++++++------------------ 1 file changed, 181 insertions(+), 167 deletions(-) diff --git a/src/asciidoc/testing.adoc b/src/asciidoc/testing.adoc index 85e7cc2bda..376e28eec9 100644 --- a/src/asciidoc/testing.adoc +++ b/src/asciidoc/testing.adoc @@ -4158,31 +4158,30 @@ coverage based on Spring MVC Test. ==== HtmlUnit Integration Spring provides integration between <> 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. -* <> - Use this option if you want the raw libraries -* <> - Use this option to ease development and be able to reuse code -between integration and end-to-end testing. -* <> - 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. +* <>: Use this option if you +want to use the raw HtmlUnit libraries. +* <>: Use this option to +ease development and reuse code between integration and end-to-end testing. +* <>: 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 <> ==== -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 <>. +The default context path is `""`. Alternatively, we can specify the context path as +illustrated in <>. ==== -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 <> 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 <> ==== -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 -<> by leveraging the Page Object -Pattern. As we mentioned in <>, we could -use the Page Object Pattern with HtmlUnit, but it is much easier now. Let's take a look at -our `CreateMessagePage`. +<> by leveraging the _Page Object +Pattern_. As we mentioned in <>, 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)] -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 -<> we got from WebDriver. -However, Geb makes things even easier by taking care of some of the boiler plate code for -us. +<> 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 <> ==== -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 -<>. The most obvious change is -that we are now using the Page Object Pattern. As we mentioned in -<>, 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 +<> 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