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 snapshot version, you need to ensure you have included the Spring Snapshot repository.

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

Then ensure you have added the dependencies:

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-test-mvc-htmlunit</artifactId>
  <version>1.0.0.BUILD-SNAPSHOT</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.42.2</version>
  <scope>test</scope>
</dependency>

<!-- necessary only if you are using Geb -->
<dependency>
  <groupId>org.gebish</groupId>
  <artifactId>geb-spock</artifactId>
  <version>0.9.2</version>
  <scope>test</scope>
</dependency>
<dependency>
  <groupId>org.spockframework</groupId>
  <artifactId>spock-spring</artifactId>
  <version>0.7-groovy-2.0</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 to use the latest snapshot, ensure you include the Spring Snapshot repository in your build.gradle:

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

Then ensure you have added the necessary dependencies:

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

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

  // necessary only if you are using Geb
  testCompile "org.gebish:geb-spock:0.9.2"
  testCompile "org.spockframework:spock-spring:0.7-groovy-2.0"
}

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

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 JUnit class that is annotated as shown below:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {WebMvcConfig.class, MockDataConfig.class})
@WebAppConfiguration
public class MockMvcHtmlUnitCreateMessageTest {

  @Autowired
  private WebApplicationContext context;

  ...
}
  • @RunWith(SpringJUnit4ClassRunner.class) allows Spring to perform dependency injection on our MockMvcHtmlUnitCreateMessageTest. This is why our @Autowired annotations will be honored.

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

  • @WebAppConfiguration indicates to SpringJUnit4ClassRunner that it should create a WebApplicationContext rather than a ApplicationContext.

Next we need to create our MockMvc instance from the context. An example of how to do this has been provided below:

@Before
public void setup() {
  MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
  ...
}

Of course this is just one way to create a MockMvc instance. We could have decided to add a Servlet Filter, use a Standalone setup, etc. The important thing is that we need an instance of MockMvc. For additional information on creating a MockMvc instance refer to the Spring MVC Test documentation.

4.2. Initializing HtmlUnit

Now that we have created the MockMvc instance, we need to create an HtmlUnit WebClient. We use the MockMvcWebConnection to ensure that HtmlUnit utilizes the MockMvc instance we created in the previous step.

private WebClient webClient;

@Before
public void setup() {
  MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).build();

  webClient = new WebClient();
  webClient.setWebConnection(new MockMvcWebConnection(mockMvc));
}

4.3. 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/mail/messages/form");
The first path segment, /mail, after the host is treated as the context root. Alternatively, we could have passed in an optional second argument to MockMvcWebConnection to specify a context root of "".

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.

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.

5.1. Why WebDriver?

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 = createMsgFormPage.getHtmlElementById("summary");
summaryInput.setValueAttribute("Spring Rocks");

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 {
  private final HtmlPage currentPage;

  ...

  public T createMessage(Class<T> resultPage, String summary, String text) {
    ...
    setSummary(currentPage, summary);
    ...
    HtmlPage result = submit.click();
    ...
    return (T) error ? new CreateMessagePage(result) : new ViewMessagePage(result);
  }

  public void setSummary(String summary) {
    HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary");
    summaryInput.setValueAttribute(summary);
  }
}

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.1.1. Creating MockMvc

In order to use WebDriver 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 JUnit class that is annotated as shown below:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {WebMvcConfig.class, MockDataConfig.class})
@WebAppConfiguration
public class MockMvcHtmlUnitDriverCreateMessageTest {

  @Autowired
  private WebApplicationContext context;

  ...
}
  • @RunWith(SpringJUnit4ClassRunner.class) allows Spring to perform dependency injection on our MockMvcHtmlUnitDriverCreateMessageTest. This is why our @Autowired annotations will be honored.

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

  • @WebAppConfiguration indicates to SpringJUnit4ClassRunner that it should create a WebApplicationContext rather than a ApplicationContext.

Next we need to create our MockMvc instance from the context. An example of how to do this has been provided below:

@Before
public void setup() {
  MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
  ...
}

Of course this is just one way to create a MockMvc instance. We could have decided to add a Servlet Filter, use a Standalone setup, etc. The important thing is that we need an instance of MockMvc. For additional information on creating a MockMvc instance refer to the Spring MVC Test documentation.

5.2. Initializing WebDriver

Now that we have created the MockMvc instance, we need to create a MockMvcHtmlUnitDriver which ensures we use the MockMvc instance we created in the previous step.

private WebDriver driver;

@Before
public void setup() {
	MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
	driver = new MockMvcHtmlUnitDriver(mockMvc, true);
}

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 messagePage = CreateMessagePage.to(driver);

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

ViewMessagePage viewMessagePage =
    messagePage.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?, 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 {
    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);
    }
}

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.

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 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. We can also 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 return a Message object in addition to the individual Message properties. This allows us to easily interact with our rich domain objects instead of just a String. 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.

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

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.

6.2.1. Creating a MockMvc instance

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,MockDataConfig])
@WebAppConfiguration
class GebCreateMessagesSpec extends GebReportingSpec {
  @Autowired
  WebApplicationContext context;

  WebDriver driver;

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

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

  • @WebAppConfiguration indicates that a WebApplicationContext should be created rather than a ApplicationContext.

Next we need to create our MockMvc instance from the context. An example of how to do this has been provided below:

def setup() {
  MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).build()
  ...
}

Of course this is just one way to create a MockMvc instance. We could have decided to add a Servlet Filter, use a Standalone setup, etc. The important thing is that we need an instance of MockMvc. For additional information on creating a MockMvc instance refer to the Spring MVC Test documentation.

6.2.2. Initializing WebDriver

Now that we have created the MockMvc instance, we need to create a MockMvcHtmlUnitDriver which ensures we use the MockMvc instance we created in the previous step. We then use Geb’s explicit lifecycle and set the driver on Geb’s Browser instance.

WebDriver driver;

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

def destroy() {
  if(driver != null) {
    driver.close();
  }
}

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

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

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:

We use an assertion in the closure, so we can determine where things went wrong if we were at the wrong page.
at CreateMessagePage

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

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.0.0. Other IDE’s should work using Gradle’s IDE support, but have not been tested.

  • IDE Setup

    • Install Spring Tool Suite 3.0.0+

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

    • Gradle Eclipse

    • Groovy Eclipse plugin

    • Groovy 1.8 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/mail/. 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.