Automating Assembly and Integration Tests with HtmlUnit

With this blog entry I want to start a mini-series about automating assembly and integration tests in Java. Mini-series because there are many different tools and techniques to choose from, and depending on the software being developed you will choose and combine to get the best results.

This entry will show the basics on how to use HtmlUnit to automate assembly and integration tests of web applications.

What is HtmlUnit? HtmlUnit is a framework that runs on top of the popular JUnit testing framework that simulates a web browser, including JavaScript. Using the HtmlUnit API you can interact with the web pages and get insights of the layout and contents to write assertions. An interesting fact about HtmlUnit is that you can use to automate tests of web applications written in other languages than Java.

HtmlUnit can be downloaded from the tool web site at http://htmlunit.sourceforge.net/.

With HtmlUnit there is no practical distinction when you intend to write an assembly or an integration test case. If you choose to run with an embedded (or in memory) database and web application server or with a dedicated one will mark the (sometimes controversial) distinction between an assembly or an integration test.

In this entry we are focusing on HtmlUnit setup and common use cases and for a later entry we will focus in assembly vs. integration setup, best practices and how to run them within a continuous build/integration process.

The web application under test

To keep the tests as simple as possible we’ve chosen a very simple web application written with JavaServer Pages, Spring MVC, Spring Framework and Hibernate. The application implements the usual CRUD use cases on an entity called Codes. This entity represents pairs of code and values with the code used as primary key. We have two pages: one to show a listing of existing records and other used to add, modify or update records.

The application will be packaged as a War and deployed to an Apache Tomcat instance. It will use an embedded HSQLDB database with some data already loaded. With this setup the tests will be integration tests.

In the figures below you can see the files structure in the Eclipse IDE and two screenshots on the two application pages.

Preparing the Integration tests project

For simplicity we are going to package our tests in a different Java project. Assuming that you have created a Java project, download HtmlUnit version 2.8 and add as dependencies all Jar files distributed with HtmlUnit. We will add then a regular JUnit test case to the project, as seen in the screenshot below.

Writing the first HtmlUnit test

Next step is to write the first test using HtmlUnit. In this test we want to verify the initial layout and contents of the Code View page.

With HtmlUnit you start by instantiating the com.gargoylesoftware.htmlunit.WebClient class and getting the page under test with the getPage method. This method will return a com.gargoylesoftware.htmlunit.html.HtmlPage instance that you can use to interact with the page and assert the layout and contents.

In the code snippet below we will use the getByXPath method of HtmlPage class to get the codes listed in the page and assert the quantity and values shown on it.

@SuppressWarnings("unchecked")
@Test
public void testCodesCrud()
    throws MalformedURLException, IOException {

    WebClient client = new WebClient();

    // first entry to view page
    HtmlPage viewPage =
        client.getPage("http://localhost:8080/sdc.tests.webapp/CodesView.do");

    assertEquals("Codes View Page", viewPage.getTitleText());

    // confirm initial data list
    List<DomAttr> codeAttrNodes =
        (List<DomAttr>) viewPage.getByXPath("//input[@name='code']/@value");
    List<String> codes = new ArrayList<String>();
    for (DomAttr attr : codeAttrNodes) {
        codes.add(attr.getTextContent());
    }

    assertEquals(3, codes.size());
    assertTrue(codes.contains("A"));
    assertTrue(codes.contains("C"));
    assertTrue(codes.contains("D"));

    client.closeAllWindows();
}

Once created, we can run the test from the IDE using the run as JUnit command as usual.

Interacting with the pages

To write really useful tests you need to interact with the page, write values to input controls, press buttons, interact with other pages or any other activity that is usually performed in web UIs. This kind of actions are easy to write with HtmlUnit. When you write more and more tests you will notice that 90% of the tests are written combining the same few constructs, so it is really easy to write a large number of tests in no much time.

The code snippet below shows how to press the Add New button in the Code View page to access the Codes Detail page, how to write a new code and value in the form and submit the new record pressing the Ok button.

    HtmlForm addForm = viewPage.getFormByName("add");
    HtmlSubmitInput addButton = addForm.getInputByName("add");

    // submit to add a new record
    HtmlPage detailPage = addButton.click();

    assertEquals("Codes Detail Page", detailPage.getTitleText());

    HtmlForm detailForm = detailPage.getFormByName("detail");
    HtmlTextInput codeField = detailForm.getInputByName("code");
    HtmlTextInput valueField = detailForm.getInputByName("value");
    HtmlSubmitInput okButton = detailForm.getInputByName("ok");

    codeField.setValueAttribute("P");
    valueField.setValueAttribute("postponed");

    // confirm add and return to view page
    viewPage = okButton.click();

To verify that the Codes View page is now listing the new record, we can use the following code snippet.

    // confirm the new record has been added
    codeAttrNodes =
        (List<DomAttr>) viewPage.getByXPath("//input[@name='code']/@value");
    codes = new ArrayList<String>();
    for (DomAttr attr : codeAttrNodes) {
        codes.add(attr.getTextContent());
    }

    assertEquals(4, codes.size());
    assertTrue(codes.contains("P"));

    List<DomAttr> valueAttrNodes =
        (List<DomAttr>) viewPage.getByXPath("//input[@name='value']/@value");
    List<String> values = new ArrayList<String>();
    for (DomAttr attr : valueAttrNodes) {
        values.add(attr.getTextContent());
    }

    assertTrue(values.contains("postponed"));

The full test case

The code listing below shows the full test case written with HtmlUnit automating the CRUD operations on the Codes entity.

package sdc.startupassets.spring25.test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.List;

import org.junit.Test;

import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.DomAttr;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput;
import com.gargoylesoftware.htmlunit.html.HtmlTextInput;

public class CodesTestCase {

    @SuppressWarnings("unchecked")
    @Test
    public void testCodesCrud()
        throws MalformedURLException, IOException {

        WebClient client = new WebClient();

        // first entry to view page
        HtmlPage viewPage =
            client.getPage("http://localhost:8080/sdc.tests.webapp/CodesView.do");

        assertEquals("Codes View Page", viewPage.getTitleText());

        // confirm initial data list
        List<DomAttr> codeAttrNodes =
            (List<DomAttr>) viewPage.getByXPath("//input[@name='code']/@value");

        List<String> codes = new ArrayList<String>();
        for (DomAttr attr : codeAttrNodes) {
            codes.add(attr.getTextContent());
        }

        assertEquals(3, codes.size());
        assertTrue(codes.contains("A"));
        assertTrue(codes.contains("C"));
        assertTrue(codes.contains("D"));

        HtmlForm addForm = viewPage.getFormByName("add");
        HtmlSubmitInput addButton = addForm.getInputByName("add");

        // submit to add a new record
        HtmlPage detailPage = addButton.click();

        assertEquals("Codes Detail Page", detailPage.getTitleText());

        HtmlForm detailForm = detailPage.getFormByName("detail");
        HtmlTextInput codeField = detailForm.getInputByName("code");
        HtmlTextInput valueField = detailForm.getInputByName("value");
        HtmlSubmitInput okButton = detailForm.getInputByName("ok");

        codeField.setValueAttribute("P");
        valueField.setValueAttribute("postponed");

        // confirm add and return to view page
        viewPage = okButton.click();

        assertEquals("Codes View Page", viewPage.getTitleText());

        // confirm the new record has been added
        codeAttrNodes =
            (List<DomAttr>) viewPage.getByXPath("//input[@name='code']/@value");

        codes = new ArrayList<String>();
        for (DomAttr attr : codeAttrNodes) {
            codes.add(attr.getTextContent());
        }

        assertEquals(4, codes.size());
        assertTrue(codes.contains("P"));

        List<DomAttr> valueAttrNodes =
            (List<DomAttr>) viewPage.getByXPath("//input[@name='value']/@value");

        List<String> values = new ArrayList<String>();
        for (DomAttr attr : valueAttrNodes) {
            values.add(attr.getTextContent());
        }

        assertTrue(values.contains("postponed"));

        HtmlForm pForm = viewPage.getFormByName("form_P");
        HtmlSubmitInput updateButton = pForm.getInputByName("update");

        // go to detail page to update record
        detailPage = updateButton.click();

        assertEquals("Codes Detail Page", detailPage.getTitleText());

        detailForm = detailPage.getFormByName("detail");
        codeField = detailForm.getInputByName("code");
        valueField = detailForm.getInputByName("value");
        okButton = detailForm.getInputByName("ok");

        valueField.setValueAttribute("updated value");

        // confirm update and return to view page
        viewPage = okButton.click();

        assertEquals("Codes View Page", viewPage.getTitleText());

        // confirm the record has been updated
        valueAttrNodes =
            (List<DomAttr>) viewPage.getByXPath("//input[@name='value']/@value");

        values = new ArrayList<String>();
        for (DomAttr attr : valueAttrNodes) {
            values.add(attr.getTextContent());
        }

        assertTrue(values.contains("updated value"));

        pForm = viewPage.getFormByName("form_P");
        HtmlSubmitInput deleteButton = pForm.getInputByName("delete");

        // go to detail page to delete record
        detailPage = deleteButton.click();

        assertEquals("Codes Detail Page", detailPage.getTitleText());

        detailForm = detailPage.getFormByName("detail");
        okButton = detailForm.getInputByName("ok");

        // confirm delete and return to view page
        viewPage = okButton.click();

        assertEquals("Codes View Page", viewPage.getTitleText());

        // confirm final data list
        codeAttrNodes =
            (List<DomAttr>) viewPage.getByXPath("//input[@name='code']/@value");
        codes = new ArrayList<String>();
        for (DomAttr attr : codeAttrNodes) {
            codes.add(attr.getTextContent());
        }

        assertEquals(3, codes.size());
        assertTrue(codes.contains("A"));
        assertTrue(codes.contains("C"));
        assertTrue(codes.contains("D"));

        client.closeAllWindows();
    }
}

Conclusion

As shown here it is very simple to setup and write automated tests for web applications. Emulating a web browser you can interact with web pages and forms with simple Java constructs and can assert the layout and contents by exploring the DOM of the rendered pages.

Author: deors

senior technology architect in accenture, with a passion for technology related stuff, celtic music and the best sci-fi, among other thousand things!

6 thoughts on “Automating Assembly and Integration Tests with HtmlUnit”

  1. no idea if I will make it work myself, in fact I an not goig to do it, but I will push some one else.
    Thanks Jorge, event I believe it will be easier using .net technologies, it will worth a try.

    1. .net has its own good things, but when it comes to quality and testing tools, i think that java wins the match. and remember that not all people can buy the visual studio ultimate with all those nice testing tools🙂.

  2. Hi!

    first: congratulations for this tutorials!

    I’m learning about htmlunit, but i can see the pictures, can you reupload?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s