This project’s aim is to provide integration between Spring MVC Test Framework and HtmlUnit. This simplifies performing end to end testing when using HTML based views. Spring MVC Test HtmlUnit enables developers to:

  • Easily test 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.

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 the Servlet Container.

1. Why Spring MVC Test HtmlUnit?

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 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.

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"));

What if we want to test our form view that allows us to create the message? For example, assume 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 do we ensure that our form will produce the correct request to create a new message? A naive attempt would look like this:

mockMvc.perform(get("/messages/form"))
    .andExpect(xpath("//input[@name='summary']").exists())
    .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:

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"));

This would reduce the risk of our test incorrectly passing, but there are still some 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).

  • 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?

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.

1.1. 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:

  • Does our page display a message 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:

  • 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.

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.

1.2. Enter Spring MVC Test HtmlUnit

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…​Spring MVC Test 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.

2. Getting Started

Here is an outline to getting started quickly.

3. Updating Dependencies

Before you use the project, you must ensure to update your dependencies. Instructions for building with Maven and Gradle have been provided below:

3.1. Building with Maven

The project is available in the Spring Maven Repository. If you are using Maven, you will want to make the following updates.

When using a milestone version, you need to ensure you have included the Spring Milestone repository.

<repository>
  <id>spring-milestone</id>
  <url>https://repo.spring.io/libs-milestone</url>
</repository>

Stable releases are included in Maven Central, so generally you do not need to include any additional repositories.

Then ensure you have added the dependencies:

<dependency>
  <groupId>org.springframework.test.htmlunit</groupId>
  <artifactId>spring-test-htmlunit</artifactId>
  <version>1.0.0.RC1</version>
  <scope>test</scope>
</dependency>

<!-- necessary only if you are using WebDriver -->
<dependency>
  <groupId>org.seleniumhq.selenium</groupId>
  <artifactId>selenium-htmlunit-driver</artifactId>
  <version>2.45.0</version>
  <scope>test</scope>
</dependency>

<!-- necessary only if you are using Geb -->
<dependency>
  <groupId>org.gebish</groupId>
  <artifactId>geb-spock</artifactId>
  <version>0.10.0</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.spockframework</groupId>
  <artifactId>spock-spring</artifactId>
  <version>1.0-groovy-2.4</version>
  <scope>test</scope>
</dependency>

3.2. Building with Gradle

The project is available in the Spring Maven Repository. If you are using Gradle, you will want to make the following updates.

If you want the latest milestone, ensure you include the Spring Milestone repository in your build.gradle:

repositories {
  maven { url 'https://repo.spring.io/libs-milestone' }
}

If you want the latest release, ensure you include the Maven Central repository in your build.gradle:

repositories {
  mavenCentral()
}

Then ensure you have added the necessary dependencies:

dependencies {
  testCompile 'org.springframework.test.htmlunit:spring-test-htmlunit:1.0.0.RC1'

  // necessary only if you are using WebDriver
  testCompile "org.seleniumhq.selenium:selenium-htmlunit-driver:2.45.0"

  // necessary only if you are using Geb
  testCompile "org.gebish:geb-spock:0.10.0"
  testCompile "org.spockframework:spock-spring:1.0-groovy-2.4"
}

4. MockMvc and HtmlUnit

Now that we have the correct dependencies, we can use HtmlUnit in our unit tests. Our example assumes you already have JUnit as a dependency. If you have not added it, please update your classpath accordingly.

The complete code sample for using HtmlUnit and Spring MVC Test can be found in MockMvcHtmlUnitCreateMessageTest.

4.1. HtmlUnit JUnit Spring Setup

In order to use HtmlUnit and Spring MVC Test we must first initialize our test. There is plenty of documentation on how to Spring’s Test support, but we will review the basic steps below.

The first step is to create a new JUnit class that is annotated as shown below:

@RunWith(SpringJUnit4ClassRunner.class) (1)
@ContextConfiguration(classes = {WebMvcConfig.class, WebSecurityConfig.class, MockDataConfig.class}) (2)
@WebAppConfiguration (3)
@WithMockUser (4)
public class MockMvcHtmlUnitCreateMessageTests {

	@Autowired
	WebApplicationContext context;

    ...
}
1 @RunWith(SpringJUnit4ClassRunner.class) allows Spring to perform dependency injection on our MockMvcHtmlUnitCreateMessageTest. This is why our @Autowired annotations will be honored.
2 @ContextConfiguration tells Spring what configuration to load. You will notice that we are loading a mock instance of our data tier to improve the performance of our tests. If we wanted, we could optionally run the tests against a real database. However, this has the disadvantages we mentioned previously.
3 @WebAppConfiguration indicates to SpringJUnit4ClassRunner that it should create a WebApplicationContext rather than a ApplicationContext.
4 @WithMockUser runs all the tests as a user name "user" with the role "ROLE_USER" using Spring Security’s test support. Obviously we don’t have to leverage the Spring Security support, but it is quite convenient that we do not need to authenticate before each tests allowing for better isolation and performance.

Next we need to create a special instance of WebClient that uses MockMvc instead of HTTP to make requests. We can use MockMvcWebClientBuilder as a simple way to do this.

WebClient webClient;

@Before
public void setup() {
    webClient = MockMvcWebClientBuilder
        .webAppContextSetup(context, springSecurity())
        // for illustration only - defaults to ""
        .contextPath("")
        .createWebClient();
}
This is a simple example of using MockMvcWebClientBuilder. For more advanced usage, refer to 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.

4.2. Using HtmlUnit

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:

HtmlPage createMsgFormPage = webClient.getPage("http://localhost/messages/form");
The the context path is "". Alternatively, we could have passed in an optional second argument to MockMvcWebConnection to specify an alternative context path.

We can then fill out the form and submit it to create a message.

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

Finally, we can verify that a new message was created successfully

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!");

This improves on our 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.

Another important factor is that HtmlUnit uses Mozilla Rhino engine to evaluate JavaScript on your pages. This means, that we can verify our JavaScript methods as well!

For the complete example, please refer to MockMvcHtmlUnitCreateMessageTest. Refer to the HtmlUnit documentation for additional information about using HtmlUnit.

4.3. Advanced MockMvcWebClientBuilder

In our example above we used MockMvcWebClientBuilder in the simplest way possible.

WebClient webClient;

@Before
public void setup() {
    webClient = MockMvcWebClientBuilder
        .webAppContextSetup(context, springSecurity())
        // for illustration only - defaults to ""
        .contextPath("")
        .createWebClient();
}

We could also perform the exact same setup using the following:

MockMvc mockMvc = MockMvcBuilders
        .webAppContextSetup(context)
        .apply(springSecurity())
        .build();

webClient = MockMvcWebClientBuilder
        .mockMvcSetup(mockMvc)
        // for illustration only - defaults to ""
        .contextPath("")
        .createWebClient();

By building the WebClient with a MockMvc instance we have the full power of MockMvc at our finger tips. Ultimately, this is simply performing the following:

For additional information on creating a MockMvc instance refer to the Spring MVC Test documentation.
String contextPath = "";
MockMvc mockMvc = MockMvcBuilders
        .webAppContextSetup(context)
        .apply(springSecurity())
        .build();

webClient = new WebClient();
webClient.setWebConnection(new MockMvcWebConnection(mockMvc, contextPath));

5. 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 WebDriver to make things even easier.

The complete code sample for using HtmlUnit and Spring MVC Test can be found in MockMvcHtmlUnitDriverCreateMessageTests.java.

5.1. 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.

Despite being a part of Selenium, WebDriver does not require a Selenium Server to run your tests.

Suppose we need to ensure that a message is created properly. The tests involve finding the html inputs, 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.

If one of the fields was named "summary", then we might have something like the following repeated everywhere within our tests:

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:

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

This ensures that if we change the UI we do not have to update all of our tests.

We might take it a step further and place this logic within an Object that represents the HtmlPage we are currently on.

public class CreateMessagePage {
  	HtmlPage currentPage;

	HtmlTextInput summaryInput;

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

Formerly, this pattern is known as the 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.

5.2. WebDriver JUnit Spring Setup

In order to use WebDriver and Spring MVC Test we must first initialize our test. There is plenty of documentation on how to Spring’s Test support, but we will review the basic steps below.

The first step is to create a new JUnit class that is annotated as shown below:

@RunWith(SpringJUnit4ClassRunner.class) (1)
@ContextConfiguration(classes = {WebMvcConfig.class, WebSecurityConfig.class, MockDataConfig.class}) (2)
@WebAppConfiguration (3)
@WithMockUser (4)
public class MockMvcHtmlUnitDriverCreateMessageTests {
	@Autowired
	WebApplicationContext context;

    ...
}
1 @RunWith(SpringJUnit4ClassRunner.class) allows Spring to perform dependency injection on our MockMvcHtmlUnitCreateMessageTest. This is why our @Autowired annotations will be honored.
2 @ContextConfiguration tells Spring what configuration to load. You will notice that we are loading a mock instance of our data tier to improve the performance of our tests. If we wanted, we could optionally run the tests against a real database. However, this has the disadvantages we mentioned previously.
3 @WebAppConfiguration indicates to SpringJUnit4ClassRunner that it should create a WebApplicationContext rather than a ApplicationContext.
4 @WithMockUser runs all the tests as a user name "user" with the role "ROLE_USER" using Spring Security’s test support. Obviously we don’t have to leverage the Spring Security support, but it is quite convenient that we do not need to authenticate before each tests allowing for better isolation and performance.

Next we need to create a special instance of WebDriver that uses MockMvc instead of HTTP to make requests. We can use MockMvcHtmlUnitDriverBuilder as a simple way to do this.

WebDriver driver;

@Before
public void setup() {
    driver = MockMvcHtmlUnitDriverBuilder
        .webAppContextSetup(context, springSecurity())
        .createDriver();
}
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.

5.3. Using WebDriver

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:

CreateMessagePage page = CreateMessagePage.to(driver);

We can then fill out the form and submit it to create a message.

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

This improves on the design of our HtmlUnit test by leveraging the Page Object Pattern. As we mentioned in Why WebDriver and MockMvc?, we could use the Page Object Pattern with HtmlUnit, but it is much easier now. Let’s take a look at our CreateMessagePage.

public class CreateMessagePage
        extends AbstractPage { (1)

    (2)
    private WebElement summary;

    private WebElement text;

    (3)
    @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:8080/messages/form");
        get(driver, "messages/form");
        return PageFactory.initElements(driver, CreateMessagePage.class);
    }
}
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 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 PageFactory allows us to remove a lot of code from HtmlUnit version of CreateMessagePage by automatically resolving each WebElement. The 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.
3 We can use the @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].

Finally, we can verify that a new message was created successfully

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 model. For example, it exposes a method that returns a Message object.

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

We can then leverage the rich domain objects in our assertions. We do this by creating a custom fest assertion that allows us to verify all the properties of the actual Message are equal to the expected Message. You can view the details of the custom assertion in Assertions and MessageAssert

Last, don’t forget to close the WebDriver instance when we are done.

@After
public void destroy() {
    if(driver != null) {
        driver.close();
    }
}

For additional information on using WebDriver, refer to the WebDriver documentation.

5.4. Advanced MockMvcHtmlUnitDriverBuilder

In our example above we used MockMvcHtmlUnitDriverBuilder in the simplest way possible.

WebDriver driver;

@Before
public void setup() {
    driver = MockMvcHtmlUnitDriverBuilder
        .webAppContextSetup(context, springSecurity())
        .createDriver();
}

We could also perform the exact same setup using the following:

MockMvc mockMvc = MockMvcBuilders
        .webAppContextSetup(context)
        .apply(springSecurity())
        .build();

driver = MockMvcHtmlUnitDriverBuilder
        .mockMvcSetup(mockMvc)
        // for illustration only - defaults to ""
        .contextPath("")
        .createDriver();

By building the WebDriver with a MockMvc instance we have the full power of MockMvc at our finger tips. Ultimately, this is simply performing the following:

For additional information on creating a MockMvc instance refer to the Spring MVC Test documentation.
String contextPath = "";
MockMvc mockMvc = MockMvcBuilders
        .webAppContextSetup(context)
        .apply(springSecurity())
        .build();

WebConnectionHtmlUnitDriver driver = new WebConnectionHtmlUnitDriver();
driver.setWebConnection(new MockMvcWebConnection(mockMvc, contextPath));

6. MockMvc and Geb

In the previous section, we saw how to use MockMvc with WebDriver. In this section, we will use Geb to make our tests more Groovy.

6.1. Why Geb and MockMvc?

Geb is backed by WebDriver, so it offers many of the same benefits we got from WebDriver. However, Geb makes things even easier by taking care of some of the boiler plate code for us. Of course we want to use MockMvc so that we do no need to deploy our code to a server. The easiest way to understand the benefits of using Geb is to jump into an example.

Another great feature of Geb is its exceptional documentation.

6.2. WebDriver Geb and Spring Setup

Once we have the correct dependencies, we can use Geb in our unit tests.

The complete code sample for using Geb and Spring MVC Test can be found in GebCreateMessagesSpec.

In order to use HtmlUnit and Spring MVC Test we must first create a MockMvc instance. There is plenty of documentation on how to create a MockMvc instance, but we will review how to create a MockMvc instance very quickly in this section.

The first step is to create a new GebReportingSpec class that is annotated as shown below:

@ContextConfiguration(classes=[WebMvcConfig, WebSecurityConfig, MockDataConfig]) (1)
@WebAppConfiguration (2)
@WithMockUser (3)
class GebCreateMessagesSpec
		extends GebReportingSpec { (4)

	@Autowired
	WebApplicationContext context;

    ...
}
For this to work ensure to add the spock-spring dependency as illustrated in the Updating Dependencies section. This is why @Autowired annotations will be honored.
1 @ContextConfiguration tells Spring what configuration to load. You will notice that we are loading a mock instance of our data tier to improve the performance of our tests. If we wanted, we could optionally run the tests against a real database. However, this has the disadvantages we mentioned previously.
2 @WebAppConfiguration indicates that a WebApplicationContext should be created rather than a ApplicationContext.
3 @WithMockUser runs all the tests as a user name "user" with the role "ROLE_USER" using Spring Security’s test support. Obviously we don’t have to leverage the Spring Security support, but it is quite convenient that we do not need to authenticate before each tests allowing for better isolation and performance.
4 We extend GebReportingSpec to provide Geb integration for Geb’s reporting functionality.

Next we need to create a special instance of WebDriver that uses MockMvc instead of HTTP to make requests. We can use MockMvcHtmlUnitDriverBuilder as a simple way to use Geb’s Explicit Driver Management facility.

def setup() {
    browser.driver = MockMvcHtmlUnitDriverBuilder
        .webAppContextSetup(context, springSecurity())
        .createDriver()
}
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.

6.3. Using Geb

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:

to CreateMessagePage

We can then fill out the form and submit it to create a message.

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

Any unrecognized method calls or property accesses/references that are not found will be 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 HtmlUnit test. The most obvious change is that we are now using the Page Object Pattern. As we mentioned in Why WebDriver and MockMvc?, we could use the Page Object Pattern with HtmlUnit, but it is much easier now.

Let’s take a look at our CreateMessagePage.

class CreateMessagePage extends Page {
	static at = { assert title == 'Messages : Create'; true }
	static url = 'messages/form'
	static content =  {
		submit { $('input[type=submit]') }
		form { $('form') }
		errors(required:false) { $('label.error, .alert-error')?.text() }
	}
}

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:

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:

then:
    at CreateMessagePage
    errors.contains('This field is required.')
We use an assertion in the closure, so 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. We can use a jQuery-ish Navigator API to select the content we are interested in.

Finally, we can verify that a new message was created successfully

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

7. Running the sample project

The following provides information on setting up a development environment that can run the sample in Spring Tool Suite 3.6.4+. Other IDE’s should work using Gradle’s IDE support, but have not been tested.

  • IDE Setup

    • Install Spring Tool Suite 3.6.4+

    • You will need the following plugins installed (can be found on the Extensions Page)

    • Gradle Eclipse

    • Groovy Eclipse plugin

    • Groovy 2.2 compiler should be enabled in Window→Preferences Groovy→Compiler

  • Importing the project into Spring Tool Suite

    • File→Import…​→Gradle Project

Any tests ending in ITest or ISpec require the application to be deployed to http://localhost:9990/. You should be able to do this easily using Eclipse WTP. Other tests run using Sprint Test MVC and do not require the application to be deployed.

8. Minimum Requirements

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 the Servlet Container.

8.1. Minimum JDK

The miniumum JDK is 1.6 Not only has Java 1.5 reached EOL, but this is necessary to keep up to date with dependencies since Selenium requires JDK 1.6.

8.2. Minimum Spring Version

The project should work with Spring 3.2.0+, but use with Spring 4.0 is encouraged.

9. Contributing

Before contributing or logging an issue please be sure to the issue does not already exist in this project’s issue tracking. If one does not exist, please create an issue.

If you see anything you’d like to change we encourage taking advantage of github’s social coding features by making the change in a fork of this repository and sending a pull request.

Before we accept a non-trivial patch or pull request we will need you to sign the contributor’s agreement. Signing the contributor’s agreement does not grant anyone commit rights to the main repository, but it does mean that we can accept your contributions, and you will get an author credit if we do. Active contributors might be asked to join the core team, and given the ability to merge pull requests.

10. License

The Spring MVC Test HtmlUnit project is available under version 2.0 of the Apache License.

11. Appendix

11.1. Migrating from 1.0.0.M2 to 1.0.0.RC1

Below is a summary of how to migrate between spring-test-htmlunit-1.0.0.M2 and spring-test-htmlunit-1.0.0.RC1. For a complete list of changes refer to the changelog.

11.1.1. New Maven Coordinates

We have changed the Group ID for the project. Refer to Updating Dependencies for details.

11.1.2. MockMvc WebClient & WebDriver Builders

This release adds builders for MockMvc and HtmlUnit integration. The builders provide a number of benefits:

Migrating to HtmlUnit Builders

In the previous release HtmlUnit was initialized with the following code:

MockMvc mockMvc = MockMvcBuilders
    .webAppContextSetup(context)
    .apply(springSecurity())
    .build();
webClient = new WebClient();
webClient.setWebConnection(new MockMvcWebConnection(mockMvc));

While this method is still supported, you can simplify your setup using:

webClient = MockMvcWebClientBuilder
    .webAppContextSetup(context, springSecurity())
    // contextPath defaults to ""
    // previous releases use to be treated as null
    .contextPath(null)
    .createWebClient();

For additional details about MockMvcWebClientBuilder see Advanced MockMvcWebClientBuilder.

Migrating to WebDriver Builders

In the previous release WebDriver was initialized with the following code:

MockMvc mockMvc = MockMvcBuilders
    .webAppContextSetup(context)
    .apply(springSecurity())
    .build();
webDriver = new MockMvcHtmlUnitDriver(mockMvc, true);

MockMvcHtmlUnitDriver was removed due to the fact that it was too complex and did not allow for more advanced configurations (i.e. context path).

Now we can configure our webDriver using the following:

webDriver = MockMvcHtmlUnitDriverBuilder
    .webAppContextSetup(context, springSecurity())
    // contextPath defaults to ""
    // previous releases use to be treated as null
    .contextPath(null)
    .createDriver();

For additional details about MockMvcHtmlUnitDriverBuilder see Advanced MockMvcHtmlUnitDriverBuilder.

Migrating to Geb Builders

Geb use to be initialized with the following:

MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).build()
browser.driver = new MockMvcHtmlUnitDriver(mockMvc, true)

MockMvcHtmlUnitDriver was removed due to the fact that it was too complex and did not allow for more advanced configurations (i.e. context path).

Now we can configure Geb using the following:

browser.driver = MockMvcHtmlUnitDriverBuilder
    .webAppContextSetup(context, springSecurity())
    .createDriver()

11.1.3. Default Context Path is now ""

Based upon community feedback, the default context path is now "". This means if you are using a previous release, you will need to update the URL that is configured. For example:

Migrating Context Path for HtmlUnit
HtmlPage createMsgFormPage =  webClient.getPage("http://localhost/context-root/messages/form");

// changes to...

HtmlPage createMsgFormPage = webClient.getPage("http://localhost/messages/form");
Migrating Context Path for WebDriver
webDriver.get("http://localhost:9990/mail/messages/form");

// changes to...

webDriver.get("http://localhost:9990/messages/form");
Migrating Context Path for Geb
baseUrl = 'http://localhost:8080/mail/'

// changes to...

baseUrl = 'http://localhost:8080/'