Clone wiki

Java EE 6-Galleria / TestingThePresentationLayer

The Presentation Layer tests

This layer is tested using integration tests for the moment, and does not use unit tests. All the tests are contained with a uber-class AllPagesIntegrationTest. Like the name implies, the class is used to test for functionality in all the pages within the application. Tests are not split into several classes at the moment, because Arquillian does not allow test classes to share deployments yet; see ARQ-197 and ARQ-567 for more details.

How do the tests work?

The tests are run by the Maven failsafe plugin in the integration-testing phase, for the reason that the execution of all tests can take quite a bit of time. The maven-glassfish-plugin is used to manage the lifecycle of the GlassFish container for the duration of the tests.

The test class is annotated with the @RunWith(Arquillian.class) and @RunAsClient annotations to denote the use of the Arquillian testrunner to execute the tests at the client. The test class performs a deployment of the application against the previously mentioned remote GlassFish container, which would have started by the time the test class instance has been created.

The deployment is defined in the @Deployment annotated method, which is used by Arquillian to create the deployment archive. The @Deployment method defined in the test class, recreate the structure of the eventual EAR file produced by the build, except for the application.xml file in the EAR root. Since we're using the Arquillian GlassFish adapter, we'll need to provide the necessary properties to enable Arquillian to drive the tests. The configuration for Arquillian will be specified in the arquillian.xml file:

    <!-- Remote Glassfish configuration -->
    <container qualifier="glassfish" default="true">
            <property name="adminPort">10048</property>

The port number here corresponds to the administration port number of the GlassFish instance, that was started by the maven-glassfish-plugin.

The presentation layer tests use the Arquillian-Drone extension to manage a WebDriver instance that is eventually used in the tests. For our tests, we'll use the FirefoxDriver, as we want to run our tests using a real driver. Apart from adding the FirefoxDriver as a dependency in the POM, we'll also need to configure Drone to specify the WebDriver class to be used:

    <!-- Firefox Driver configuration -->
    <extension qualifier="webdriver">
        <property name="implementationClass">org.openqa.selenium.firefox.FirefoxDriver</property>

Arquillian injects the WebDriver instance for the @Drone annotated field in the test class:

    protected WebDriver driver;

The WebDriver instance is provided to instances of classes that implement the PageObject pattern. The constructor of every such class accepts a WebDriver instance, and uses it to verify if the web-browser is at the web page corresponding to the page object. A WebDriverWait instance is used to wait for the page object to enter into a pre-defined valid state. The PageUtilities class contains the visibilityOfElementLocated utility method to aid in determining the visibility of an element on the page. In conjunction with the WebDriverWait instance, one can wait for a page-specific element to be visible, to aid in page state verification in the constructor. Thus, creating a Page Object will also verify the state of the Page Object, preventing the tests from creating and using Page Objects that do not match the current state of the browser:

public class LoginPage

    private WebDriver driver;
    private URI contextPath;

    public LoginPage(WebDriver driver, URI contextPath)
        this.driver = driver;
        this.contextPath = contextPath;
        Wait<WebDriver> wait = new WebDriverWait(driver, 15);
        if (!driver.getTitle().equals("Log in to Galleria"))
            throw new IllegalStateException("This page is not the login page.");

Every class implementing the Page Object pattern, provides methods representing actions that are possible on that page. For instance, the LoginPage quite naturally allows you to log into the application:

    public HomePage loginAs(String userId, String password)
        WebElement userIdField = driver.findElement("LoginForm:userid"));
        WebElement passwordField = driver.findElement("LoginForm:password"));
        WebElement submitButton = driver.findElement("LoginForm:submit"));
        return new HomePage(driver, contextPath);

For now, it is sufficient to know that the method accepts a user Id and a password, applies these inputs to the fields on the web page, and then attempts to sign in by clicking the submit button on the page. The method then returns a reference to a HomePage instance, for the user is expected to land in the home page after successfully signing in.

Each test involves the use of these Page objects to perform some operation on the page, followed by a verification phase that may involve extracting messages present in a particular element on the page. Usually Page Objects will provide a getErrorMessagesDisplayed or a similarly named method, that would parse the page to obtain the error messages:

    public String[] getLoginErrorMessagesDisplayed()
        List<String> errorMessages = new ArrayList<String>();
        for (WebElement element : driver.findElements(By.xpath("//ul[@id='messages']/li")))
        return errorMessages.toArray(new String[0]);

Page Objects may also provide a fetchSuccessMessages or a similarly named method to obtain the list of messages corresponding to successful completion of an action:

    public String[] fetchSuccessMessages()
        List<String> errorMessages = new ArrayList<String>();
        for (WebElement element : driver.findElements(By.xpath("//ul[@id='messages']/li[@class='infoStyle']")))
        return errorMessages.toArray(new String[0]);

It should be noted that DbUnit is used in the tests, again to serve the purpose of resetting the test database before every test method is run.

A note on code coverage with Jacoco

The integration tests are executed at the client, and not in-container. The Arquillian-Jacoco extension does not instrument the archive created using a @Deployment method, atleast not yet for tests that are run at the client.

Therefore, to account for this feature's absence, we'll obtain the coverage data ourselves by connecting to the configured Jacoco agent on the GlassFish container, and write the coverage data out to the jacoco.exec file. This behavior is implemented in the writeCoverageData method of the AllPagesIntegrationTest class, which is invoked from the @AfterClass annotated method.

The coverage data written out can now be consumed by a reporting tool like Sonar. See the related section on gathering test coverage for more details.