Spring provides integration between MockMvc and HtmlUnit. This makes it easier to perform end-to-end testing when using HTML-based views. This integration allows you to:

  • Easily test HTML pages using tools like HtmlUnit, WebDriver and Geb, without the need to deploy to a servlet container.

  • Test JavaScript inside pages.

  • Optionally use mock services to speed up testing.

  • Ensure that end-to-end tests in the container and integration tests outside the container share logic.

MockMvc works on templating technologies that are independent of the servlet container (for example, Thymeleaf, FreeMarker and others), but it does not work with JSP technology because it depends on the servlet container.

Why is HtmlUnit integration needed?

The most obvious question that comes to mind is: “Why is it needed?”. The easiest way to find the answer is to study a very clear example application. Let's assume that you have a Spring MVC web application that supports CRUD operations on a Message object. The application also supports paging of all messages. How to test it?

Using Spring MVC Test, we can easily check if we can create a Message, like this:

Java

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

@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 need to test a form submission that allows us to create a message? For example, let's say our form looks like the following snippet:


<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 can we make our form prompt correctly to create a new message? A naive attempt might look like this:

Java

mockMvc.perform(get("/messages/form"))
        .andExpect(xpath("//input[@name='summary']").exists())
        .andExpect(xpath("//textarea[@name='text']").exists());
Kotlin

mockMvc.get("/messages/form").andExpect {
    xpath("//input[@name='summary']") { exists() }
    xpath("//textarea[@name='text']") { exists() }
}

This test has a few obvious shortcomings. If we update our controller to use the message parameter instead of text, our form test will pass even if the HTML form is out of sync with the controller. To solve this problem, we can combine our two tests as follows:

Java

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

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 will reduce the risk of our test passing incorrectly, but there are still some problems:

  • What to do if there are several forms on the page? Of course, we can update our XPath expressions, but they become increasingly complex as we consider more factors: Are the fields the right type? Are the fields enabled? And so on.

  • Another problem is that we are doing twice as much work as expected. We need to first validate the view and then submit that view with the same parameters we just validated. Ideally, all this can be done at once.

  • Finally, there are still some things we can't take into account. For example, what if the form has JavaScript validation that we also want to test?

A common problem is that testing a web page does not involve a one-time interaction. Instead, it is a combination of how a user interacts with a web page and how that web page interacts with other resources. For example, the result of a form submission is used as input for the user to create a message. In addition, our form submission can potentially use additional resources that affect the logic of the page, such as JavaScript validation.

Integration testing to help?

To solve the problems mentioned above, we can perform end-to-end integration testing, but it has several disadvantages. Let's look at testing a view that allows us to view messages. We may need to test the following:

  • Does our page display a notification to the user that there are no results if posts are empty?

  • Does our page properly display one single message?

  • Does our page properly support paging?

To set up these tests, we need to make sure that our database contains the necessary messages. This introduces a number of additional problems:

  • Ensuring that the correct messages are in the database can be a time-consuming process. (Given foreign key constraints).

  • Testing can be slow because each test needs to ensure that the database is in the correct state.

  • Because our database must be in a certain state, we cannot run tests in parallel.

  • Running assertions on items such as auto-generated IDs, timestamps, etc. can be difficult.

These problems do not mean that end-to-end integration testing should be abandoned completely. Instead, we can reduce the number of end-to-end integration tests by refactoring our detailed tests to use mock services that are much faster, more reliable, and without side effects. You can then run a small number of true end-to-end integration tests that test simple workflows to make sure everything works as expected.

Introducing HtmlUnit Integration

How do we strike a balance between testing how our pages and maintaining proper performance in our test suite? Answer: "By integrating MockMvc with HtmlUnit."

HtmlUnit Integration Options

If you need to integrate MockMvc with HtmlUnit, there are several options:

  • MockMvc and HtmlUnit: Use this option if you want to use raw HtmlUnit libraries.

  • MockMvc and WebDriver: Use this option to facilitate code development and reuse between integration and end-to-end testing phases.

  • MockMvc and Geb: Use this option if you need to use Groovy for testing, make it easier development and reuse code between the integration and end-to-end testing phases.

MockMvc and HtmlUnit

This section describes how to integrate MockMvc and HtmlUnit. Use this option if you want to use the raw HtmlUnit libraries.

Configuring MockMvc and HtmlUnit

First, make sure you have activated the test dependency on net.sourceforge.htmlunit:htmlunit. To use HtmlUnit with Apache HttpComponents 4.5+, you must use HtmlUnit 2.18 or higher.

You can easily create a WebClient from HtmlUnit that will be integrated with MockMvc using MockMvcWebClientBuilder as shown below:

Java

WebClient webClient;
@BeforeEach
void setup(WebApplicationContext context) {
    webClient = MockMvcWebClientBuilder
            .webAppContextSetup(context)
            .build();
}
Kotlin

lateinit var webClient: WebClient
@BeforeEach
fun setup(context: WebApplicationContext) {
    webClient = MockMvcWebClientBuilder
            .webAppContextSetup(context)
            .build()
}
This is a simple example of using MockMvcWebClientBuilder. For advanced usage, see "Advanced usage ofMockMvcWebClientBuilder".

This way we can ensure that any URL referencing localhost as the server will be directed to our MockMvc instance without the need for actual HTTP - connection. Requesting any other URL occurs through the network connection as usual. This allows us to easily test the use of content delivery (and distribution) networks (CDNs).

Using MockMvc and HtmlUnit

Now we can use HtmlUnit as standard, but without the need to deploy our application in a servlet container. For example, you could ask the view to create a message like this:

Java
HtmlPage createMsgFormPage = webClient.getPage("http://localhost/messages/form");
Kotlin
val createMsgFormPage = webClient.getPage("http://localhost/messages/form")
The default context path is "". You can also specify the path to the context, as described in "Advanced Usage of MockMvcWebClientBuilder".

Once we have a link to HtmlPage, we can immediately fill out the form and submit it to create a message, as shown in the following example:

Java

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

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, you can make sure that a new message was successfully created. The following assertions use the AssertJ library:

Java
 
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
 
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 previous code improves our test MockMvc in several ways. First, we no longer need to explicitly validate our form and then create a request that looks like a form. Instead, we request the form, fill it out and submit it, thereby significantly reducing processing delay.

Another important factor is that HtmlUnit uses the Mozilla Rhinoengine to calculate the output of JavaScript. This means we can also test the JavaScript logic on our pages.

More information about using HtmlUnit See documentation on using HtmlUnit.

Advanced using MockMvcWebClientBuilder

So far in the examples we have used MockMvcWebClientBuilder in the simplest way, creating a WebClient based on a WebApplicationContext , loaded for us by the Spring TestContext Framework. This approach is repeated in the following example:

Java

WebClient webClient;
@BeforeEach
void setup(WebApplicationContext context) {
    webClient = MockMvcWebClientBuilder
            .webAppContextSetup(context)
            .build();
}
Kotlin

lateinit var webClient: WebClient
@BeforeEach
fun setup(context: WebApplicationContext) {
    webClient = MockMvcWebClientBuilder
            .webAppContextSetup(context)
            .build()
}

We can also specify additional configuration options, as shown in the following example:

Java

WebClient webClient;
@BeforeEach
void setup() {
    webClient = MockMvcWebClientBuilder
        // demonstrates the use of MockMvcConfigurer (Spring Security) .
        webAppContextSetup(context, springSecurity())
        // for example only - default ""
        .contextPath("")
        // MockMvc is used by default only for localhost;
        // next part will use MockMvc also for example.com and example.org
        .useMockMvcForHosts("example.com","example.org")
        .build();
}
Kotlin

lateinit var webClient: WebClient
@BeforeEach
fun setup() {
    webClient = MockMvcWebClientBuilder
        // demonstrates using MockMvcConfigurer (Spring Security)
        .webAppContextSetup(context, springSecurity())
        // for example only - default ""
        .contextPath("")
        // By default MockMvc is used for localhost only;
        // next part will use MockMvc also for example.com and example.org
        .useMockMvcForHosts("example.com","example.org")
        .build()
}

Alternatively, you can do the exact same setup by configuring a MockMvc instance separately and passing it to the MockMvcWebClientBuilder as shown below:

Java

MockMvc mockMvc = MockMvcBuilders
    .webAppContextSetup(context)
    .apply(springSecurity())
    .build();
webClient = MockMvcWebClientBuilder
        .mockMvcSetup(mockMvc)
        // for example only - default ""
        .contextPath("")
        // By default MockMvc is used for localhost only;
        // next part will use MockMvc also for example.com and example.org
        .useMockMvcForHosts("example.com","example.org")
        .build();
Kotlin
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed

This is more overloaded code, but by creating a WebClient using a MockMvc instance, we have all the functionality of MockMvc at our disposal.

More information about instantiating MockMvc can be found in "Settings options".

MockMvc and WebDriver

In the previous sections, we learned about using MockMvc in combination with the raw HtmlUnit APIs. This section uses additional abstractions in Selenium WebDriver, which makes everything even simpler.

Why WebDriver and MockMvc?

We can already use HtmlUnit and MockMvc, so why do we need WebDriver? Selenium WebDriver provides an extremely elegant API that will allow us to easily organize our code. To better show how this works, we'll look at an example in this section.

Even though WebDriver is part of Selenium, it does not require Selenium Server to run your tests.

Let's say we need to make sure that the message is created correctly. The tests involve finding HTML form input elements, filling them out, and executing various assertions.

This approach results in many separate tests because we need to check error conditions as well. For example, we want an error to be thrown when filling out only part of the form. If we fill out the entire form, a newly created message should appear afterwards.

If one of the fields were named "summary", we could end up with something similar to the one below, repeated in several places in our tests:

Java

HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary");
summaryInput.setValueAttribute(summary);
Kotlin

val summaryInput = currentPage.getHtmlElementById("summary")
summaryInput .setValueAttribute(summary)

What happens if we change id to smmry? This will force us to update all our tests to take this change into account. This violates the Don't Repeat Yourself (DRY) principle, so ideally we should separate this code into a separate method, as shown below:

Java

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

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)
}

This ensures that we don't have to update all our tests if we change the UI.

We can even go one step further and put this logic in a Object that represents the HtmlPage that we are currently on, as shown in the following example:

Java

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

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()
        }
    }
}

This pattern was previously known as the (Page Object Pattern). Of course, you can do this with HtmlUnit, but WebDriver provides some tools that make implementing this pattern much easier, and we'll look at them in the following sections.

Configuring MockMvc and WebDriver

To use Selenium WebDriver with the Spring MVC Test framework, make sure your project contains a test dependency on org.seleniumhq.selenium:selenium-htmlunit-driver.

You can easily create a Selenium WebDriver that integrates with MockMvc using MockMvcHtmlUnitDriverBuilder, as shown in the following example:

Java

WebDriver driver;
@BeforeEach
void setup(WebApplicationContext context) {
    driver = MockMvcHtmlUnitDriverBuilder
            .webAppContextSetup(context)
            .build();
}
Kotlin

lateinit var driver: WebDriver
@BeforeEach
fun setup(context: WebApplicationContext) {
    driver = MockMvcHtmlUnitDriverBuilder
            .webAppContextSetup(context)
            .build()
}
This is a simple example of using MockMvcHtmlUnitDriverBuilder . For advanced usage, see " Advanced use of MockMvcHtmlUnitDriverBuilder".

In the previous example, any URL referencing localhost as the server is guaranteed to be directed to our MockMvc instance without the need for an actual HTTP connection . Requesting any other URL occurs through the network connection as usual. This allows us to easily test the use of content delivery (and distribution) networks (CDNs).

Using MockMvc and WebDriver

We can now use WebDriver as standard, but without the need to deploy our application in a container servlets. For example, you could ask the view to create a message like this:

Java
CreateMessagePage page = CreateMessagePage.to(driver);
Kotlin
val page = CreateMessagePage.to(driver)

Then you can fill out the form and submit it to create a message, as follows:

Java

ViewMessagePage viewMessagePage =
        page.createMessage(ViewMessagePage.class, expectedSummary, expectedText);
Kotlin

val viewMessagePage =
    page.createMessage(ViewMessagePage::class, expectedSummary, expectedText)

This will improve the structure of our HtmlUnit test by using the Page Object Pattern. As already mentioned in the subsection "Why WebDriver and MockMvc? "We can use the Page Object Pattern with HtmlUnit, but with WebDriver it's still easier. Consider the following implementation of CreateMessagePage:

Java

public class CreateMessagePage
        extends AbstractPage { 
    
    private WebElement summary;
    private WebElement text;
    
    @FindBy(css = "input[type=submit]")
    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 AbstractPage . We won't go into detail about AbstractPage, but in short, it contains common functionality for all our pages. For example, if our application has a navigation bar, global error messages, and other functionality, we can place this logic in a common place.
  2. We have a member variable for each part of the HTML page that we are interested in . They are of type WebElement. PageFactory from WebDriver allows us to remove a lot of unnecessary code from HtmlUnit- version of CreateMessagePage, automatically resolving each WebElement. Method PageFactory#initElements(WebDriver,Class<T>) automatically resolves each WebElement using the field name and searching for it by id or name element in the HTML page.
  3. You can use @FindBy annotation to override the default search logic. Our example shows how to use the @FindBy annotation to find our submit button using the css(input[type=submit]) selector.
Kotlin

class CreateMessagePage(private val driver: WebDriver) : AbstractPage(driver) { 
    
    private lateinit var summary: WebElement
    private lateinit var text: WebElement
    
    @FindBy(css = "input[type=submit]")
    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 AbstractPage. We won't go into detail about AbstractPage, but in short, it contains common functionality for all our pages. For example, if our application has a navigation bar, global error messages, and other functionality, we can place this logic in a common place.
  2. We have a member variable for each part of the HTML page that we are interested in . They are of type WebElement. PageFactory from WebDriver allows us to remove a lot of unnecessary code from HtmlUnit- version of CreateMessagePage, automatically resolving each WebElement. Method PageFactory#initElements(WebDriver,Class<T>)automatically resolves each WebElement using the field name and searching for it by id or name element in the HTML page.
  3. You can use @FindBy annotation to override the default search logic. Our example shows how to use the @FindBy annotation to find our submit button using the css(input[type=submit]) selector.

Finally, you can verify that the new message was successfully created. The following assertions use the AssertJ assertion library:

Java

assertThat(viewMessagePage.getMessage()).isEqualTo(expectedMessage);
assertThat(viewMessagePage.getSuccess()).isEqualTo("Successfully created a new message");
Kotlin

assertThat(viewMessagePage.message).isEqualTo(expectedMessage)
assertThat(viewMessagePage.success).isEqualTo("Successfully created a new message")

We see that our ViewMessagePage page allows us to interact with our custom domain model. For example, it opens a method that returns a Message object:

Java

public Message getMessage() throws ParseException {
    Message message = new Message ();
    message.setId(getId());
    message.setCreated(getCreated());
    message.setSummary(getSummary());
    message.setText(getText());
    return message;
}
Kotlin
fun getMessage() = Message(getId(), getCreated(), getSummary() , getText())

We can then use full-featured domain objects in our statements.

Finally, don't forget to close the WebDriver instance when the test is completed, as shown below:

Java

@AfterEach
void destroy() {
    if (driver != null) {
        driver .close();
    }
}
Kotlin

@AfterEach
fun destroy() {
    if (driver != null) {
        driver. close()
    }
}

For more information on using WebDriver, see the Selenium documentation WebDriver.

Advanced use of MockMvcHtmlUnitDriverBuilder

So far in the examples we used MockMvcHtmlUnitDriverBuilder in the simplest way possible, creating a WebDriver based on the WebApplicationContext loaded for us by the Spring TestContext Framework. This approach is repeated here as follows:

Java

WebDriver driver;
@BeforeEach
void setup(WebApplicationContext context) {
    driver = MockMvcHtmlUnitDriverBuilder
            .webAppContextSetup(context)
            .build();
}
Kotlin

lateinit var driver: WebDriver
@BeforeEach
fun setup(context: WebApplicationContext) {
    driver = MockMvcHtmlUnitDriverBuilder
            .webAppContextSetup(context)
            .build()
}

We can also specify additional configuration options as shown below:

Java

WebDriver driver;
@BeforeEach
void setup() {
    driver = MockMvcHtmlUnitDriverBuilder
            // demonstrates the use of MockMvcConfigurer (Spring Security)
            .webAppContextSetup(context, springSecurity()) // for example only - default ""
            .contextPath("")
            // MockMvc is used by default only for localhost;
            // next part will use MockMvc also for example.com and example.org
            .useMockMvcForHosts("example.com","example.org")
            .build();
}
Kotlin

lateinit var driver: WebDriver
@BeforeEach
fun setup() {
    driver = MockMvcHtmlUnitDriverBuilder
            // demonstrates using MockMvcConfigurer (Spring Security)
            .webAppContextSetup(context, springSecurity())
            // for example only - default ""
            .contextPath("")
            // By default MockMvc is used for localhost only;
            // next part will use MockMvc also for example.com and example.org
            .useMockMvcForHosts("example.com","example.org")
            .build()
}

Alternatively, you can do the exact same setup by configuring the MockMvc instance separately and passing it to the MockMvcHtmlUnitDriverBuilder like this:

Java

MockMvc mockMvc = MockMvcBuilders
        .webAppContextSetup(context)
        .apply(springSecurity())
        .build();
driver = MockMvcHtmlUnitDriverBuilder
        .mockMvcSetup(mockMvc)
        // for example only - default ""
        .contextPath("")
        // By default MockMvc is used only for localhost;
        // next part will use MockMvc also for example.com and example.org
        .useMockMvcForHosts("example.com","example.org")
        .build();
Kotlin
// Not possible in Kotlin until https://youtrack.jetbrains.com/issue/KT-22208 is fixed

This is more overloaded code, but by creating a WebDriver using a MockMvc instance, we have all the functionality of MockMvc at our disposal.

More information about instantiating MockMvc can be found in "Settings options".

MockMvc and Geb

In the previous section, we looked at how to use MockMvc with WebDriver. In this section, we use Geb to make our tests even more Groovy-centric.

Why Geb and MockMvc?

Geb is supported by WebDriver, so it provides many of the same benefits which we receive from WebDriver. However, Geb makes things even easier by running the boilerplate code for us.

Configuring MockMvc and Geb

You can easily initialize Browser from Geb with Selenium WebDriver, which uses MockMvc, as follows:


def setup() {
    browser.driver = MockMvcHtmlUnitDriverBuilder
        .webAppContextSetup(context)
        .build()
}
This is a simple example of using MockMvcHtmlUnitDriverBuilder. For advanced usage, see " Advanced use of MockMvcHtmlUnitDriverBuilder".

This way we can ensure that any URL referencing localhost as the server will be directed to our MockMvc instance without the need for actual HTTP - connection. Requesting any other URL occurs through the network connection as usual. This allows us to easily test the use of content delivery (and distribution) networks (CDNs).

Using MockMvc and Geb

We can now use Geb as standard, but without the need to deploy our application in a container servlets. For example, you could ask the view to create a message like this:

to CreateMessagePage

You can then fill out the form and submit it to create the message as follows:


when:
form.summary = expectedSummary
form.text = expectedMessage
submit.click( ViewMessagePage)

Any unrecognized method calls, property calls, or references that were not found are passed to the current page object. This eliminates a lot of boilerplate code required when using WebDriver directly.

As with direct use of WebDriver, this is how we improve the structure of our HtmlUnit test by using the Page Object Pattern. As mentioned earlier, you can use the Page Object Pattern with HtmlUnit and WebDriver, but with Geb everything gets even more complicated. Let's take a look at our new Groovy-based CreateMessagePage implementation:

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 AbstractPage. We won't go into detail about Page, but in short, it contains common functionality for all our pages. We define the URL where this page can be found. This allows us to navigate to the page as shown below:

to CreateMessagePage

We also have a at closure that determines whether we are on the specified page. It should return true if we are on the correct page. That's why we can confirm that we are on the right page as follows:


then:
at CreateMessagePage
errors.contains('This field is required.')
We use an assertion in the closure so that we can determine where things went wrong if we end up on the wrong page.

Next, we create a content closure that specifies all the places of interest on the page. You can use jQuery-like navigator API to selecting the content we are interested in.

Finally, we can verify that the new message was created successfully, as follows:


then:
at ViewMessagePage
success == 'Successfully created a new message'
id
date
summary == expectedSummary
message == expectedMessage

More information on how to get the most out of Geb can be found in the user manual entitled "The Book of Geb".