Aggregate Test Coverage Report for Gradle Multi-Module Project

twittergoogle_plusrss

Facebooktwittergoogle_plusredditlinkedinmail

I am going to explain how to aggregate test coverage report for Gradle multi-module project. For measuring test coverage, we will use JaCoCo. Example project will use TravisCI build server and will submit coverage report to Coveralls.io.

Multi-Module project is project which creates various modules in single build, typically JARs in Java world. Such project structure is handy for splitting monolithic projects into decoupled pieces. But wait a second. Didn’t Microservices fever put monoliths into architecture anti-pattern position? No, it didn’t.

Problem

We are also building monolithic application at Dotsub. Build system of our choice is Gradle and build server is TravisCI. We use Coveralls.io to store our test coverage reports. When our code-base grew to the point when we needed to separate concerns into separate modules, I ran into problems around gathering test coverage. On Coveralls.io, you can submit only one coverage report per TravisCI build. As we wanted to have whole project built with single build, our only option was merging test coverage reports for each module and submit such aggregated report.

Example Project Structure

Our example project will have 4 modules. It is hosted in this Github repository.

.
├── build.gradle
├── .coveralls.yml
├── gradlecoverage
│   ├── build.gradle
│   └── src
│       ├── main
│       │   └── java
│       │       └── net
│       │           └── lkrnac
│       │               └── blog
│       │                   └── gradlecoverage
│       │                       └── GradleCoverageApplication.java
│       └── test
│           └── java
│               └── net
│                   └── lkrnac
│                       └── blog
│                           └── gradlecoverage
│                               └── GradleCoverageApplicationTest.java
├── module1
│   ├── build.gradle
│   └── src
│       ├── main
│       │   └── java
│       │       └── net
│       │           └── lkrnac
│       │               └── blog
│       │                   └── gradlecoverage
│       │                       └── Module1Service.java
│       └── test
│           └── java
│               └── net
│                   └── lkrnac
│                       └── blog
│                           └── gradlecoverage
│                               └── Module1ServiceTest.java
├── module2
│   ├── build.gradle
│   └── src
│       ├── main
│       │   └── java
│       │       └── net
│       │           └── lkrnac
│       │               └── blog
│       │                   └── gradlecoverage
│       │                       └── Module2Service.java
│       └── test
│           └── java
│               └── net
│                   └── lkrnac
│                       └── blog
│                           └── gradlecoverage
│                               └── Module2ServiceTest.java
├── moduleCommon
│   ├── build.gradle
│   └── src
│       ├── main
│       │   └── java
│       │       └── net
│       │           └── lkrnac
│       │               └── blog
│       │                   └── gradlecoverage
│       │                       └── CommonService.java
│       └── test
│           └── java
│               └── net
│                   └── lkrnac
│                       └── blog
│                           └── gradlecoverage
│                               └── CommonServiceTest.java
├── settings.gradle
└── .travis.yml

There is main module gradlecoverage, which is dependent on module1 and module2. Last moduleCommon is shared dependency for module1 and module2. Each module contains simple string concatenation logic with test. The code is very basic, so we are not going to explain it here. You can inspect it on Github.

Each test intentionally doesn’t cover some test cases. This way we will prove that test coverage reports are accurate.

Module Build Script

Each module has very simple build script. Example of module1/gradle.properties follows:

jar {
    baseName = 'module1'
}

dependencies {
    compile project(':moduleCommon')
}

The script just defines JAR module name and imports other module as dependency. Other scripts have very similar build scripts.

Main Build Script

First of all we need to import all sub-modules in settings.properties:

include 'moduleCommon'
include 'module1'
include 'module2'
include 'gradlecoverage'

In main script we will apply jacoco and coveralls plugins. JaCoCo plugin will be needed for aggregating coverage reports form sub-modules. We will create custom task for aggregation. Coveralls plugin will submit aggregated report to Coveralls.io.

plugins {
    id 'jacoco'
    id 'com.github.kt3k.coveralls' version '2.6.3'
}

repositories {
    mavenCentral()
}

Next we configure Java and import dependencies for sub-modules in subprojects section:

subprojects {
    repositories {
        mavenCentral()
    }

    apply plugin: 'java'
    apply plugin: "jacoco"

    group = 'net.lkrnac.blog'
    version = '1.0-SNAPSHOT'

    sourceCompatibility = JavaVersion.VERSION_1_8
    targetCompatibility = JavaVersion.VERSION_1_8

    dependencies {
        testCompile("junit:junit:4.12")
    }
}

For each sub-module, Java plugin will build JAR artifact. Each module is tested by JUnit framework. Test coverage is measured by JaCoCo plugin.

Aggregate Test Coverage Reports

Last piece of build configuration will aggregate test coverage reports:

def publishedProjects = subprojects.findAll()

task jacocoRootReport(type: JacocoReport, group: 'Coverage reports') {
    description = 'Generates an aggregate report from all subprojects'

    dependsOn(publishedProjects.test)

    additionalSourceDirs = files(publishedProjects.sourceSets.main.allSource.srcDirs)
    sourceDirectories = files(publishedProjects.sourceSets.main.allSource.srcDirs)
    classDirectories = files(publishedProjects.sourceSets.main.output)
    executionData = files(publishedProjects.jacocoTestReport.executionData)

    doFirst {
        executionData = files(executionData.findAll { it.exists() })
    }

    reports {
        html.enabled = true // human readable
        xml.enabled = true // required by coveralls
    }
}

coveralls {
    sourceDirs = publishedProjects.sourceSets.main.allSource.srcDirs.flatten()
    jacocoReportPath = "${buildDir}/reports/jacoco/jacocoRootReport/jacocoRootReport.xml"
}

tasks.coveralls {
    dependsOn jacocoRootReport
}

First we read all sub-modules into publishedProjects variable. After that we define custom aggregation jacocoRootReport task. It is inherited from JacocoReport task type. This task will be dependent on test task of each module from publishedProjects. This makes sure that test coverage reports for sub-modules are gathered for aggregation before jacocoRootReport is executed. Next we configure necessary directories for JaCoCo engine and gather all execution data from sub-modules.

Lastly we configure what kind of output we want to generate. Configuration xml.enabled = true will create aggregated report which will be submitted to Coveralls.io via coveralls task.

TravisCI Manifest

TravisCI Manifest (.travis.yml) file configure Java environment and runs single command to build and submit aggregated test coverage report by executing coveralls task:

language: java
jdk:
  - oraclejdk8

install: echo "skip './gradlew assemble' step"

script: ./gradlew build coveralls --continue

Coveralls Manifest

To be able to submit report to coveralls, we need to define Coveralls.io repository token in file .coveralls.yml:

repo_token: JQofR7TNqCMebvHgE8wHwF6rznjvEc0Fr

Final Report

Final report can be found here:

aggregate test coverage report

As you can see, there are all classes from separate modules. The only problem is that we can’t fond which module class belong to from such coverage report. But it’s not a big deal to me.

Credits

Custom aggregation task was inspired by build script for Caffeine project. It is caching library.

twittergoogle_plusrss

Component Object pattern example

twittergoogle_plusrss

Facebooktwittergoogle_plusredditlinkedinmail

Previously I showed how we at Dotsub use Page Object pattern for Selenium testing. But we use one more abstraction to make end-to-end test more maintainable. We call it Component Object pattern. This blog post will explain it on example.

Web development is shifting more and more towards reusable components. Frameworks like React, Polymer, Angular, Ember and others provide various component friendly abstractions to make front-end code-bases more maintainable. So our web applications are now full of “widgets” that have same behavior. We can use component various times on single web page or re-use it on various web pages.

Therefore it is logical to create abstraction which covers functionality of single component and reuse it across end-to-end tests. As I mentioned before, we going to call it Component Object pattern. So when we have various same components on single web page, we are going to use various Component Objects of same type per Page Object.

Example project for testing

Now we need to application we are going to test. Example application is hosted in this Github repository. You can run it by executing command from root directory:

./gradlew bootRun

From this command, you can find that it is Spring Boot application based on Gradle build system. In Dotsub, we are using Spring Boot + Java on back-end, but in fact there is no back-end mentioned example web application. We don’t need back-end for our demonstration.

For UI, we going to use React + Redux combo. I chose famous Dan Abramov’s Todos example to demonstrate Component Object pattern on. But I needed to amend it a little bit to reuse components. Without re-usable components we couldn’t demonstrate Component Object pattern.

After visiting URL http://localhost:8080, we can see following page:

Component Object pattern

There are two input components with buttons. One creates item in Todo list and second creates item in Grocery list:

Component Object pattern

When you click on item, it will mark it as completed (strike-through). When you click on completed item, it will become active again. Last element on the page is filter. You can show only active items:

Component Object pattern

or only completed items:

Component Object pattern

UI code is hosted in this Github repository, located under folder src/main/ui. I will leave this code for self study because the implementation is not deeply relevant for end-to-end testing code. Important fact for us is that input components / list components for Todo and Grocery lists should have same behavior. Thus they can be covered by reusable Component Objects pattern. Interesting is that they doesn’t necessary need to be implemented as same component in UI code.

The only important for our testing are CSS classes of particular components:

  • AddTodo input component is using CSS class add-todo
  • TodoList is using CSS class todo-list
  • AddGroceryItem input component is using CSS class add-grocery-item
  • GroceryList is using CSS class grocery-list

Note that goal of this blog post isn’t to explain Selenium or its APIs. It is expected for reader to be slightly familiar with them already.

Component Object for adding the item

First explained component object will control adding the item:

package net.lkrnac.blog.pageobject.e2e;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;

class AddItemComponent {
    private WebDriver driver;
    private String containerCssSelector;

    AddItemComponent(WebDriver driver, String containerCssSelector) {
        this.driver = driver;
        this.containerCssSelector = containerCssSelector;
    }

    AddItemComponent addItem(String todo) {
        WebElement input = driver.findElement(By.cssSelector(containerCssSelector + " input"));
        input.sendKeys(todo);
        WebElement button = driver.findElement(By.cssSelector(containerCssSelector + " button"));
        button.click();
        return this;
    }
}

Alongside Selenium web driver instance, this component object also accepts CSS selector of component it’s going to control. It has just one method addItem, which enters a text into input field and clicks Add button. It creates new item.

Component Object for item list

package net.lkrnac.blog.pageobject.e2e;

import org.apache.commons.lang3.StringUtils;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;

import java.util.List;

import static java.lang.String.format;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

class ItemsListComponent {
    private final WebDriver driver;
    private final String containerCssSelector;

    ItemsListComponent(WebDriver driver, String containerCssSelector) {
        this.driver = driver;
        this.containerCssSelector = containerCssSelector;
    }

    ItemsListComponent clickOnItem(String todoItem) {
        findElementWithText(todoItem).click();
        return this;
    }

    ItemsListComponent verifyItemShown(String todoItem, boolean expectedStrikethrough) {
        WebElement todoElement = findElementWithText(todoItem);
        assertNotNull(todoElement);
        boolean actualStrikethrough = todoElement.getAttribute("style").contains("text-decoration: line-through;");
        assertEquals(expectedStrikethrough, actualStrikethrough);
        return this;
    }

    ItemsListComponent verifyItemNotShown(String todoItem) {
        assertTrue(findElementsWithText(todoItem).isEmpty());
        return this;
    }

    private WebElement findElementWithText(String text) {
        return driver.findElement(getConditionForText(text));
    }

    private List<WebElement> findElementsWithText(String text) {
        return driver.findElements(getConditionForText(text));
    }

    private By getConditionForText(String text) {
        String containerClassName = StringUtils.substring(containerCssSelector, 1);
        return By.xpath(format("//*[@class='" + containerClassName + "']//*[text()='%s']", text));
    }
}

Similar to AddItemComponent, ItemsListComponent also takes Selenium web driver instance and CSS selector of belonging component as constructor parameters. It exposes function clickOnItem for which clicks on particular item. Other two non-private methods are used to verify if particular item is shown (verifyItemShown) or hidden (verifyItemNotShown).

Page Object using Component Objects

Now it’s time to explain Page Object using mentioned Component Objects:

package net.lkrnac.blog.pageobject.e2e;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

import static java.lang.String.format;

class ItemsPageObject {
    private final WebDriver driver;
    private final WebDriverWait wait;
    private final ItemsListComponent todoItemsList;
    private final AddItemComponent addTodoItemComponent;
    private final ItemsListComponent groceryItemsList;
    private final AddItemComponent addGroceryItemComponent;

    ItemsPageObject(WebDriver driver) {
        this.driver = driver;
        this.wait = new WebDriverWait(driver, 10);
        todoItemsList = new ItemsListComponent(driver, ".todo-list");
        addTodoItemComponent = new AddItemComponent(driver, ".add-todo");
        groceryItemsList = new ItemsListComponent(driver, ".grocery-list");
        addGroceryItemComponent = new AddItemComponent(driver, ".add-grocery-item");
    }

    ItemsPageObject get() {
        driver.get("localhost:8080");
        wait.until(ExpectedConditions.elementToBeClickable(By.tagName("button")));
        return this;
    }

    ItemsPageObject selectAll() {
        findElementWithText("All").click();
        return this;
    }

    ItemsPageObject selectActive() {
        findElementWithText("Active").click();
        return this;
    }

    ItemsPageObject selectCompleted() {
        findElementWithText("Completed").click();
        return this;
    }

    ItemsPageObject addTodo(String todoName) {
        addTodoItemComponent.addItem(todoName);
        return this;
    }

    ItemsPageObject addGroceryItem(String todoName) {
        addGroceryItemComponent.addItem(todoName);
        return this;
    }

    ItemsListComponent getTodoList() {
        return todoItemsList;
    }

    ItemsListComponent getGroceryList() {
        return groceryItemsList;
    }

    private WebElement findElementWithText(String text) {
        return driver.findElement(getConditionForText(text));
    }

    private By getConditionForText(String text) {
        return By.xpath(format("//*[text()='%s']", text));
    }
}

In constructor it creates component objects with correct CSS selectors. As you can see various instances of same component object type are used to control similar components on the page. Method get opens the page and waits until it’s loaded. Methods selectAll, selectActive, selectCompleted are used to control filter component on the page. Methods addTodo and addGroceryItem are used to enter new item into particular list. Finally getters getTodoList and getGroceryList are useful to let test class enable control over list components. Exposing component object instances to directly seemed easier than wrapping all their functions in Page Object.

Test Cases using Page Object with Page Components

I believe final test cases are readable and doesn’t require comments (which is result of using Page Object + Page Component patterns):

package net.lkrnac.blog.pageobject.e2e;

import io.github.bonigarcia.wdm.ChromeDriverManager;
import net.lkrnac.blog.pageobject.TodoApplication;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.WebIntegrationTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = TodoApplication.class)
@WebIntegrationTest
public class ItemsAppTest {
    private static WebDriver driver;

    @BeforeClass
    public static void setUp() {
        ChromeDriverManager.getInstance().setup();
        driver = new ChromeDriver();
    }

    @AfterClass
    public static void tearDown() {
        driver.quit();
    }

    @Test
    public void testCreateTodos() {
        // GIVEN
        new ItemsPageObject(driver).get()

            // WHEN
            .addTodo("Buy groceries")
            .addTodo("Tidy up")

            // THEN
            .getTodoList()
            .verifyItemShown("Buy groceries", false)
            .verifyItemShown("Tidy up", false);
    }

    @Test
    public void testCompleteTodo() {
        // GIVEN
        new ItemsPageObject(driver).get()
            .addTodo("Buy groceries")
            .addTodo("Tidy up")
            .getTodoList()

            // WHEN
            .clickOnItem("Buy groceries")

            // THEN
            .verifyItemShown("Buy groceries", true)
            .verifyItemShown("Tidy up", false);
    }

    @Test
    public void testSelectTodosActive() {
        // GIVEN
        ItemsPageObject todoPage = new ItemsPageObject(driver).get();

        todoPage
            .addTodo("Buy groceries")
            .addTodo("Tidy up")
            .getTodoList()
            .clickOnItem("Buy groceries");

        // WHEN
        todoPage
            .selectActive()

            // THEN
            .getTodoList()
            .verifyItemNotShown("Buy groceries")
            .verifyItemShown("Tidy up", false);
    }

    @Test
    public void testSelectTodosCompleted() {
        // GIVEN
        ItemsPageObject todoPage = new ItemsPageObject(driver).get();
        todoPage
            .addTodo("Buy groceries")
            .addTodo("Tidy up")
            .getTodoList()
            .clickOnItem("Buy groceries");

        // WHEN
        todoPage
            .selectCompleted()

            // THEN
            .getTodoList()
            .verifyItemShown("Buy groceries", true)
            .verifyItemNotShown("Tidy up");
    }

    @Test
    public void testSelectTodosAll() {
        // GIVEN
        ItemsPageObject todoPage = new ItemsPageObject(driver).get();
        todoPage
            .addTodo("Buy groceries")
            .addTodo("Tidy up")
            .getTodoList()
            .clickOnItem("Buy groceries");
        todoPage
            .selectCompleted()

            // WHEN
            .selectAll()

            // THEN
            .getTodoList()
            .verifyItemShown("Buy groceries", true)
            .verifyItemShown("Tidy up", false);
    }

    @Test
    public void testCreateGroceryItems() {
        // GIVEN
        new ItemsPageObject(driver).get()

            // WHEN
            .addGroceryItem("avocados")
            .addGroceryItem("tomatoes")

            // THEN
            .getGroceryList()
            .verifyItemShown("avocados", false)
            .verifyItemShown("tomatoes", false);
    }

    @Test
    public void testCompleteGroceryItem() {
        // GIVEN
        new ItemsPageObject(driver).get()
            .addGroceryItem("avocados")
            .addGroceryItem("tomatoes")
            .getGroceryList()

            // WHEN
            .clickOnItem("avocados")

            // THEN
            .verifyItemShown("avocados", true)
            .verifyItemShown("tomatoes", false);
    }

    @Test
    public void testSelectGroceryItemsActive() {
        // GIVEN
        ItemsPageObject todoPage = new ItemsPageObject(driver).get();

        todoPage
            .addGroceryItem("avocados")
            .addGroceryItem("tomatoes")
            .getGroceryList()
            .clickOnItem("avocados");

        // WHEN
        todoPage
            .selectActive()

            // THEN
            .getGroceryList()
            .verifyItemNotShown("avocados")
            .verifyItemShown("tomatoes", false);
    }

    @Test
    public void testSelectGroceryItemsCompleted() {
        // GIVEN
        ItemsPageObject todoPage = new ItemsPageObject(driver).get();
        todoPage
            .addGroceryItem("avocados")
            .addGroceryItem("tomatoes")
            .getGroceryList()
            .clickOnItem("avocados");

        // WHEN
        todoPage
            .selectCompleted()

            // THEN
            .getGroceryList()
            .verifyItemShown("avocados", true)
            .verifyItemNotShown("tomatoes");
    }

    @Test
    public void testSelectGroceryItemsAll() {
        // GIVEN
        ItemsPageObject todoPage = new ItemsPageObject(driver).get();
        todoPage
            .addGroceryItem("avocados")
            .addGroceryItem("tomatoes")
            .getGroceryList()
            .clickOnItem("avocados");
        todoPage
            .selectCompleted()

            // WHEN
            .selectAll()

            // THEN
            .getGroceryList()
            .verifyItemShown("avocados", true)
            .verifyItemShown("tomatoes", false);
    }

    @Test
    public void testSelectCombinedItemsActive() {
        // GIVEN
        ItemsPageObject todoPage = new ItemsPageObject(driver).get();

        todoPage
            .addTodo("Buy groceries")
            .addTodo("Tidy up")
            .addGroceryItem("avocados")
            .addGroceryItem("tomatoes");

        todoPage
            .getGroceryList()
            .clickOnItem("avocados");

        todoPage
            .getTodoList()
            .clickOnItem("Tidy up");

        // WHEN
        todoPage
            .selectActive();

        // THEN
        todoPage
            .getTodoList()
            .verifyItemShown("Buy groceries", false)
            .verifyItemNotShown("Tidy up");

        todoPage
            .getGroceryList()
            .verifyItemNotShown("avocados")
            .verifyItemShown("tomatoes", false);
    }
}
twittergoogle_plusrss

Select Video.js subtitle track automatically

twittergoogle_plusrss

Facebooktwittergoogle_plusredditlinkedinmail

We at Dotsub are using videojs as video player for our sites. One of the Video.js main benefits is customizability via its plugin system. Recently we had a need to automatically select certain subtitle track after user started video on Video.js player. This is handy when we know the language user is most probably going to need translation into. So we created simple open-source plugin to save few user clicks needed for selecting default subtitle track.

The plugin was named videojs-select-subtitle and is hosted on Github. Assuming you know how to use Video.js plugin for you video player, we jump straight to explaining how to configure this new plugin.

Installation

Most modern JavaScript projects are using some kind of Node.JS based build process with NPM dependency management. So easiest way to install plugin is to use NPM:

npm i --save videojs-select-subtitle

Your other option is to clone Github project build the project with command:

npm run build

This command creates JavaScript files in sub-directory dist. You can include minified or non-minified version of JavaScript plugin file into your project with whatever mechanism you are used to.

Usage

After after videojs player is initialized and plugin is installed in our project, we can execute it with command:

player.selectSubtitle({ trackLanguage: 'es' });

Object passed as parameter into selectSubtitle function is option required by plugin to select correct subtitle/caption track. Options object has to have trackLanguage key and value needs to define language attribute of caption/subtitle track to be selected. Of course such track must be available in videojs player, otherwise plugin can’t select it at all. Both key and value of options object are case sensitive.

Usage on Brightcove Video Cloud

Video.js project was created by Brightcove. Therefore they made it very easy to embed Video.JS plugins into their Video Cloud service. If you are using this service as your online video platform, you can install videojs-subtitle-plugin via their UI.

In order to do this, you need to host built plugin file somewhere on the internet. After plugin is accessible by Brightcove Video Cloud, you can refer to it from your Brightcove player. It is important to configure name of the plugin to selectSubtitle and trackLanguage option for the plugin:

Screenshot_2016-09-02_14-18-59

After the user starts video, plugin finds desired caption/subtitle track and show it automatically.

twittergoogle_plusrss

Page Object pattern example

twittergoogle_plusrss

Facebooktwittergoogle_plusredditlinkedinmail

This blog post will provide example of end-to-end Selenium tests in Java with usage of Page Object pattern.

Every responsible developer knows that end-to-end test is necessary at some point. When you want to test web application end-to-end, there is only one decent open-source option: Selenium. Of course end-to-end test suite have to be maintainable. To achieve that, I believe we have to use Page Object pattern for each web screen present in web application. Martin Fowler’s link explains very well benefits of this pattern, therefore I skip theory.

We at Dotsub are using Java platform with Spring Boot for back-end. Therefore Java Selenium APIs were natural fit for our needs. This blog post will explain Page Object pattern on application built with Spring Boot. For UI, it will use famous Dan Abramov’s Todos  example. This example was used for Getting started with Redux tutorial. It is simple single page application built on top of React + Redux JavaSript frameworks. It provides us decent functionality we can cover by Selenium test examples.

Functionality for testing

Let’s take a look at functionality of this simple example.

Page Object: todos-all

In text Input field, we can enter name of Todo item. Button Add Todo saves item into Todos list. When we click on any Todo item we can mark it as complete or incomplete.

Page Object: todos-active

With Show buttons below the Todos list, we can filter items to show only Active

Page Object: todos-complete

…or Completed Todo items.

In fact this Todos example application doesn’t communicate with Spring Boot server. Front-end assets are just served from Spring Boot application. But we don’t need such communication to demonstrate Page Object example. I used Spring Boot, because it’s trivial to integrate Selenium tests with it. Note that goal of this blog post isn’t to explain Selenium or its APIs. It is expected for reader to be slightly familiar with them already.

Page Object class

As we mentioned, our end-to-end test will use Page object pattern. Goal of this pattern is to separate HTML details of testing page from actions that can be performed against the page. Listing that shows Page Object class for Todos application will be split into few parts, so that we can explain each part separately:

package net.lkrnac.blog.pageobject.e2e;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

import java.util.List;

import static java.lang.String.format;
import static org.junit.Assert.*;


public class TodoPageObject {
    private WebDriver driver;
    private WebDriverWait wait;

    public TodoPageObject(WebDriver driver) {
        this.driver = driver;
        this.wait = new WebDriverWait(driver, 10);
    }

    public TodoPageObject get() {
        driver.get("localhost:8080");
        wait.until(ExpectedConditions.elementToBeClickable(By.tagName("button")));
        return this;
    }

Page Object class has two fields. First is Selenium WebDriver instance, which represents browser’s window wrapper used for controlling web page similar to real user. WebDriverWait is helper for waiting until HTML elements are rendered. Method get is used to load Todos page based on it’s URL.

    private WebElement findElementWithText(String text) {
        return driver.findElement(getConditionForText(text));
    }

    private List<WebElement> findElementsWithText(String text) {
        return driver.findElements(getConditionForText(text));
    }

    private By getConditionForText(String text) {
        return By.xpath(format("//*[text()='%s']", text));
    }

These three methods are private helpers for Todos actions shown later. We will used them to find HTML element or list of HTML elements based on the text they contain. For text search we use Selenium’s XPath APIs.

    public TodoPageObject addTodo(String todo) {
        WebElement input = driver.findElement(By.tagName("input"));
        input.sendKeys(todo);
        WebElement button = driver.findElement(By.tagName("button"));
        button.click();
        return this;
    }

    public TodoPageObject clickOnTodoItem(String todoItem) {
        findElementWithText(todoItem).click();
        return this;
    }

I believe names addTodo and clickOnTodoItem are self explanatory. We will use them to enter test into Todos input list, click Add Todo button and to mark Todo items complete/incomplete. Notice that return value is Todos Page Object instance, so that we can use fluent API during testing. This mechanism will keep our tests less verbose.

    public TodoPageObject selectAll() {
        findElementWithText("All").click();
        return this;
    }

    public TodoPageObject selectActive() {
        findElementWithText("Active").click();
        return this;
    }

    public TodoPageObject selectCompleted() {
        findElementWithText("Completed").click();
        return this;
    }

These three methods are using for clicking on filter buttons All/Active/Complete.

    public TodoPageObject verifyTodoShown(String todoItem, boolean expectedStrikethrough) {
        WebElement todoElement = findElementWithText(todoItem);
        assertNotNull(todoElement);
        boolean actualStrikethrough = todoElement.getAttribute("style").contains("text-decoration: line-through;");
        assertEquals(expectedStrikethrough, actualStrikethrough);
        return this;
    }

    public TodoPageObject verifyTodoNotShown(String todoItem) {
        assertTrue(findElementsWithText(todoItem).isEmpty());
        return this;
    }
}

Last two methods are used to verify if given Todo item is shown on not shown. When it’s shown we can verify if it’s completed via parameter expectedStrikethrough.

Tests

With such page object support we can start testing:

package net.lkrnac.blog.pageobject.e2e;

import io.github.bonigarcia.wdm.ChromeDriverManager;
import net.lkrnac.blog.pageobject.TodoApplication;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.boot.test.WebIntegrationTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = TodoApplication.class)
@WebIntegrationTest
public class TodoAppTest {
    private static WebDriver driver;

    @BeforeClass
    public static void setUp() {
        ChromeDriverManager.getInstance().setup();
        driver = new ChromeDriver();
    }

    @AfterClass
    public static void tearDown() {
        driver.quit();
    }

    @Test
    public void testCreateTodos() {
        // GIVEN
        new TodoPageObject(driver).get()

            // WHEN
            .addTodo("testTodo1")
            .addTodo("testTodo2")

            // THEN
            .verifyTodoShown("testTodo1", false)
            .verifyTodoShown("testTodo2", false);
    }

    @Test
    public void testCompleteTodo() {
        // GIVEN
        new TodoPageObject(driver).get()
            .addTodo("testTodo1")
            .addTodo("testTodo2")

            // WHEN
            .clickOnTodoItem("testTodo1")

            // THEN
            .verifyTodoShown("testTodo1", true)
            .verifyTodoShown("testTodo2", false);
    }

    @Test
    public void testSelectActive() {
        // GIVEN
        new TodoPageObject(driver).get()
            .addTodo("testTodo1")
            .addTodo("testTodo2")
            .clickOnTodoItem("testTodo1")

            // WHEN
            .selectActive()

            // THEN
            .verifyTodoNotShown("testTodo1")
            .verifyTodoShown("testTodo2", false);
    }

    @Test
    public void testSelectCompleted() {
        // GIVEN
        new TodoPageObject(driver).get()
            .addTodo("testTodo1")
            .addTodo("testTodo2")
            .clickOnTodoItem("testTodo1")

            // WHEN
            .selectCompleted()

            // THEN
            .verifyTodoShown("testTodo1", true)
            .verifyTodoNotShown("testTodo2");
    }

    @Test
    public void testSelectAll() {
        // GIVEN
        new TodoPageObject(driver).get()
            .addTodo("testTodo1")
            .addTodo("testTodo2")
            .clickOnTodoItem("testTodo1")
            .selectCompleted()

            // WHEN
            .selectAll()

            // THEN
            .verifyTodoShown("testTodo1", true)
            .verifyTodoShown("testTodo2", false);
    }
}

These test cases cover some basic scenarios real user could perform against our Todos application. I am not going to explain them, because they are nicely readable due to usage of Page Object pattern. Separate huge benefit of this pattern I want to point out is decoupling tests from HTML. If we would change HTML structure on Todos page, we would need to change only Page Object class. Test cases would remain untouched.

Example code with all project files is hosted on Github.

twittergoogle_plusrss

How to verify equality without equals method

twittergoogle_plusrss

Facebooktwittergoogle_plusredditlinkedinmail

I don’t like equals method. It often requires very ugly code. Therefore I try to avoid need for it as much as possible. But how to compare objects during testing? This blog post will cover it.

You may argue, that there are various possibilities to generate equals method or make it more maintainable. For example:

But what about testing it? I met so many developers that were surprised after hearing about unit testing of equals method. So should we unit test it? YES, of course! If it’s generated by IDE, it often has a lot of null check if statements. Testing such code means covering a lot of test scenarios. Also static code analyzer will probably complain about cyclomatic complexity violations.

If you are using object with custom equals method in HashMap or HashSet, we need to conform to hashCode vs equals contract. Eventual issues in this contract can lead into very tricky behavior of our map or set. Therefore I rather use some unique String or numeric representation as key in the map so that I don’t need to create equals. HashSet is type I try to avoid completely.

So if most common reasons for creating equals method for production code are avoided, what about testing? Verifications in tests often require to compare if objects have fields with same values. Of course it doesn’t make sense to create equals only for testing assertions. Sometimes we also have nested types to compare. And what about comparison of arrays or lists during testing?

Luckily there is very neat library called Unitils. It provides various testing helpers for Hibernate, Database and I/O testing or mocking. But I was using only module called Reflection assert. It makes comparison of Java objects during testing piece of cake.

Example Objects

package net.lkrnac.blog.reflectioncompare;

public class Address {
    private String line1;
    private String line2;
    private String city;
    private String postalCode;

    public Address(String line1, String line2, String city, String postalCode) {
        this.line1 = line1;
        this.line2 = line2;
        this.city = city;
        this.postalCode = postalCode;
    }

    public String getLine1() {
        return line1;
    }

    public String getLine2() {
        return line2;
    }

    public String getCity() {
        return city;
    }

    public String getPostalCode() {
        return postalCode;
    }
}
package net.lkrnac.blog.reflectioncompare;

public class Person {
    private String firstName;
    private String lastName;
    private Address address;

    public Person(String firstName, String lastName, Address address) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.address = address;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public Address getAddress() {
        return address;
    }
}

Notice that Address is nested field in Person class.

Reflection equals verification

Now let’s take a look how we would compare instances of above listed types.

package net.lkrnac.blog.reflectioncompare;

import org.junit.Test;

import static org.unitils.reflectionassert.ReflectionAssert.assertReflectionEquals;

public class VerifyObjectsTest {
    @Test
    public void testObjectsSuccess() {
        Address expectedAddress = new Address("Barad-dûr", "Mount Doom", "Mordor", "1");
        Person expectedPerson = new Person("Sauron", null, expectedAddress);

        Address actualAddress = new Address("Barad-dûr", "Mount Doom", "Mordor", "1");
        Person actualPerson = new Person("Sauron", null, actualAddress);

        assertReflectionEquals(expectedPerson, actualPerson);
    }

    @Test
    public void testObjectsFail() {
        Address expectedAddress = new Address("Barad-dûr", null, "Mordor", "1");
        Person expectedPerson = new Person("Sauron", null, expectedAddress);

        Address actualAddress = new Address("Barad-dûr", "Mount Doom", "Mordor", "1");
        Person actualPerson = new Person("Sauron", null, actualAddress);

        assertReflectionEquals(expectedPerson, actualPerson);
    }
}

First test method created two separate instances of Person and compares them. Second test method intentionally creates difference in nested Address field between actual and expected, so that we can explore Unitils output:

junit.framework.AssertionFailedError: 
Expected: Person<firstName="Sauron", lastName=null, address=Address<line1="Barad-dûr", line2=null, city="Mordor", postalCode="1">>
  Actual: Person<firstName="Sauron", lastName=null, address=Address<line1="Barad-dûr", line2="Mount Doom", city="Mordor", postalCode="1">>

--- Found following differences ---
address.line2: expected: null, actual: "Mount Doom"

--- Difference detail tree ---
 expected: Person<firstName="Sauron", lastName=null, address=Address<line1="Barad-dûr", line2=null, city="Mordor", postalCode="1">>
   actual: Person<firstName="Sauron", lastName=null, address=Address<line1="Barad-dûr", line2="Mount Doom", city="Mordor", postalCode="1">>

address expected: Address<line1="Barad-dûr", line2=null, city="Mordor", postalCode="1">
address   actual: Address<line1="Barad-dûr", line2="Mount Doom", city="Mordor", postalCode="1">

address.line2 expected: null
address.line2   actual: "Mount Doom"

Very nice output making the nested difference very obvious.

Comparison of arrays and collections

Another very useful use case of Unitils Reflections assert module is comparison of arrays and collections:

package net.lkrnac.blog.reflectioncompare;

import org.junit.Test;
import org.unitils.reflectionassert.ReflectionComparatorMode;

import static java.util.Arrays.asList;
import static org.unitils.reflectionassert.ReflectionAssert.assertReflectionEquals;

public class VerifyArraysTest {

    @Test
    public void testCompareArraysSuccess() {
        String [] expectedArray = new String []{"string1", "string2", "string3"};
        String [] actualArray = new String []{"string1", "string3", "string2"};

        assertReflectionEquals(expectedArray, actualArray, ReflectionComparatorMode.LENIENT_ORDER);
    }

    @Test
    public void testCompareArraysFail() {
        String [] expectedArray = new String []{"string1", "string2", "string3"};
        String [] actualArray = new String []{"string1", "string3", "string2"};

        assertReflectionEquals(expectedArray, actualArray);
    }

    @Test
    public void testCompareCollectionsSuccess() {
        String [] expectedArray = new String []{"string1", "string2", "string3"};
        String [] actualArray = new String []{"string1", "string3", "string2"};

        assertReflectionEquals(asList(expectedArray), asList(actualArray), ReflectionComparatorMode.LENIENT_ORDER);
    }

    @Test
    public void testCompareCollectionsFail() {
        String [] expectedArray = new String []{"string1", "string2", "string3"};
        String [] actualArray = new String []{"string1", "string3", "string2"};

        assertReflectionEquals(asList(expectedArray), asList(actualArray));
    }
}

First test method is comparing arrays. It will succeed, because we instructed Unitils to ignore order of elements with parameter ReflectionComparatorMode.LENIENT_ORDER. In second test method, we don’t use reflection comparator mode parameter, so order matters. Because it’s is different for actual and expected arrays, test will fail with this output:

junit.framework.AssertionFailedError: 
Expected: ["string1", "string2", "string3"]
  Actual: ["string1", "string3", "string2"]

--- Found following differences ---
[1]: expected: "string2", actual: "string3"
[2]: expected: "string3", actual: "string2"

--- Difference detail tree ---
 expected: ["string1", "string2", "string3"]
   actual: ["string1", "string3", "string2"]

[1] expected: "string2"
[1]   actual: "string3"

[2] expected: "string3"
[2]   actual: "string2"

Again very detailed output about test failure.

Lastly there are also included test methods comparing lists. So Unitils Reflection assert module can be easily used also for Java collections.

Example code is hosted on Github.

twittergoogle_plusrss