Spring MVC Test with Geb

Engineering | Rob Winch | April 15, 2014 | ...

In my third post I discussed how to use WebDriver to make designing our tests easier with the Page Object Pattern. In this post, I'm going to discuss how to use Geb to make our testing with MockMvc more Groovy.

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.


NOTE: Another great feature of Geb is its exceptional documentation.


Updating Dependencies

Before you use the project, you must ensure to update your dependencies. Instructions for both Maven and Gradle can be found on the site documentation.

Using Geb

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

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.

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

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:


NOTE: 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

Feedback please!

If you have feedback on this blog series or the Spring Test MVC HtmlUnit, I encourage you to reach out via the comments below, github issues, or ping me on twitter @rob_winch. Of course the best feedback comes in the form of contributions.

Get the Spring newsletter

Stay connected with the Spring newsletter

Subscribe

Get ahead

VMware offers training and certification to turbo-charge your progress.

Learn more

Get support

Tanzu Spring offers support and binaries for OpenJDK™, Spring, and Apache Tomcat® in one simple subscription.

Learn more

Upcoming events

Check out all the upcoming events in the Spring community.

View all