"Beauty is a sign of intelligence." - Andy Warhol
To this point, we’ve focused entirely on elements that cannot be seen. In this chapter we bring everything home by exposing our backend services to the end-user.
When it comes to Enterprise Java, we have our fill of options for display technologies. The Java EE Specification provides JavaServer Faces (JSF), a component-based framework for web applications. This approach takes advantage of server-side rendering; that is, the final response returned to the client is created on the server from source templates (typically Facelets).
In our GeekSeek example, however, we’ll be going off the beaten path a bit and rolling our own single-page application in pure HTML. The dynamic elements backed by data will be supplied via JavaScript calls to the backend via the RESTful interface we’d exposed earlier.
In general our requirements remain to be simply: expose our operations in a human-consumable format.
On a high level, we’re looking to allow a user to take advantage of the application’s primary purpose; we’d like to modify state of our domain objects in a consistent fashion. We may state these:
* As a User I should be able Add/Change/Delete a Conference * As a User I should be able Add/Change/Delete a Session to Conferences * As a User I should be able Add/Change/Delete a Attachment to Sessions and Conferences * As a User I should be able Add/Change/Delete a Venue (and attach to Conference and Session)
Our frontend is written using the populare JavaScript framework AngularJS; A framework that lets you extend the HTML syntax, write client side components in the familiar Model, View, Controller(MVC) pattern and allow for a two way binding of our data models.
AngularJS has a built-in abstraction to work with Resources like REST, but it lacks built-in support for HATEOAS. The AngularJS Resource can easily operate on a single Resource, but with no automatic link support or knowledge of the OPTIONS that the Resource might support. For our frontend view to be completely driven by the backend services we need this extra layer of support.
To address HATEOAS in the client view we’ve created a simple object we call RestGraph. The main responsibillity of this object is to discover linked Resources in the Response and determine what we’re allowed to do with the gicen Resource.
Without exposing too much of how exatly the RestGraph is implemented, we’ll just give you a short overview of what it can do and how it is useful.
To start of, the RestGrahp require you to define a root Resource URL, the top level Resource of the graph.
var root = RestGraph('http://geekseek.continuousdev.org/api').init();
This is similar to how you would do it when you visit a web page in a web browser; you give your self and the browser a starting point by typing in an address(URL) in the address bar.
Based on the Response from the root Resource we can determine what can be done next.
{
"link": [
{
"rel": "conference",
"href": "http://geekseek.continuousdev.org/api/conference",
"mediaType": "application/vnd.ced+json; type=conference"
},
{
"rel": "whoami",
"href": "http://geekseek.continuousdev.org/api/security/whoami",
"mediaType": "application/vnd.ced+json; type=user"
}
]
}
In this example the start Resource only contain links to other resources and contain no data it self. We can choose to discover where to go next by looking at all available links. Maybe let the user decide what path to take?
var paths = root.links
Or choose to follow the graph down a desired path by fetching a named relation.
var conference = root.getLink('conference')
Some function calls on the conference instance can be mapped directly to the REST verbs for the given Resource: GET, DELETE, PATCH, PUT.
conference.get()
conference.remove()
conference.add({})
conference.update({})
Now we have the basics. But, we still don’t know if we’re allowed to perform all of those operations on all discovered Resources. There are certain security constraints and possible other limitations implementated on the server side that we need to take into consideration before/when we make a Request. In the same fashion we discover related resources and actions via links in the Response, we can query the Resource for the OPTIONS it supports.
> OPTIONS /api/conference
< Allow: GET, OPTIONS, HEAD
If the user performing the Request is authenticated the Response to the OPTIONS query might look like this:
> OPTIONS /api/conference
< Allow: GET, PATCH, OPTIONS, HEAD
The server just told us that as an authenticated user you’re allowd to perform a PATCH operation on this Resource as well as a GET operation. We’re now not only restricted to a Read Only view, but have full Read/Write access.
The RestGraph hides the usage of OPTIONS to query the server for allowed actions behind a meaningful API.
conference.canGet()
conference.canUpdate()
conference.canRemove()
conference.canCreate()
This gives us the complete picture of what the API and the backend, under the current circumstances, will allow us to do. While the communication with the backend is up and running, the user still can’t see any thing. We need to convert the raw data into a suitable user interface.
We choose to map the MediaTypes described in the DAP to HTML templates in the UI.
{
"rel": "conference",
"href": "http://geekseek.continuousdev.org/api/conference",
"mediaType": "application/vnd.ced+json; type=conference"
}
By combining the current Action (View, Update, Create) with the MediaType sub type argument we can identify a unique template for how to represent the current Resource as HTML.
As an example, the Conference MediaType in View mode could look like this:
<div class="single" data-ng-if="isSingle">
<div class="well">
<div class="pull-right">
<a data-ng-show="resource.canUpdate()" data-ng-click="edit()">
<i class="icon-edit-sign"></i></a>
<a data-ng-show="resource.canRemove()" data-ng-click="remove()">
<i class="icon-remove-sign"></i></a>
</div>
<h1>{{resource.data.name}} <small>{{resource.data.tagLine}}</small></h1>
<p class="date">
<abbr title="{{resource.data.start|date:medium}}" class="start">
<span class="day">{{resource.data.start|date:'d'}}</span>
</abbr>
<span class="sep">-</span>
<abbr title="{{resource.data.end|date:medium}}" class="end">
<span class="day">{{resource.data.end|date:'d'}}</span>
<span class="month">{{resource.data.end|date:'MMMM'}}</span>
<span class="year">{{resource.data.end|date:'yyyy'}}</span>
</abbr>
</p>
<div class="attendees pull-right">
<subresource parent="resource" link="attendees" />
</div>
</div>
<subresource parent="resource" link="session" />
</div>
The UI for our GeekSeek application is based on a JavaScript front end talking to a REST backend. In this scenario, there are some different approaches and types of testing we can do; one is for the pure JavaSscript code (e.g. client controllers) and the other part is the interaction with the browser and REST endpoints on the backend.
For the pure client JavaScript we’re going to use QUnit, a JavaScript Unit Testing framework. And handily enough, Arquillian has an extension that can invoke QUnit execution within our normal Java build system.
While the QUnit tests themselves do not require any Java code, the Arquillian QUnit extension uses a normal JUnit test class to configure and report on the QUnit execution.
Our UI code contains a graph that can hold the state of the various REST responses and their links. In this test scenario we want to test that the graph can understand the response returned from a REST service given an OPTIONS request.
We start by configuring the QUnit Arquillian runner in a simple JUnit Java class:
@RunWith(QUnitRunner.class)
@QUnitResources("src")
public class GraphTestCase {
@QUnitTest("test/resources/assets/tests/graph/graph-assertions.html")
public void testGraph() {
// empty body
}
}
In the above example we introduce two new annotations that are specific to the Arquillian QUnit extension;
-
@QUnitResources defines the root source of the javascript files
-
@QUnitTest defines which HTML page to 'run' for this @Test
The graph-assertions.html referenced in the @QUnitTest annotation is the HTML page that contains the <script> tag which includes the QUnit JavaScript tests and any other JavaScript dependencies we might need.
<html>
<head>
<title>QUnit Test Suite</title>
<link rel="stylesheet" href="http://code.jquery.com/qunit/qunit-1.12.0.css" type="text/css" media="screen">
<script src="http://code.jquery.com/jquery-1.8.2.min.js"></script>
<script type="text/javascript"
src="http://code.jquery.com/qunit/qunit-1.12.0.js"></script>
<script type="text/javascript"
src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.0rc1/angular.js"></script>
<script type="text/javascript"
src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.0rc1/angular-route.js"></script>
<script type="text/javascript"
src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.0rc1/angular-mocks.js"></script>
<script type="text/javascript"
src="../../../../../main/resources/META-INF/resources/webjars/core/graph.js"></script>
<script type="text/javascript" src="assert.js"></script>
</head>
<body>
<h1 id="qunit-header">QUnit Test Suite</h1>
<h2 id="qunit-banner"></h2>
<div id="qunit-testrunner-toolbar"></div>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>
</body>
</html>
Our assert.js is then free to contain the QUnit functions which define our client-side test suite:
module("Service OPTIONS", optionsInit)
asyncTest("can get?", 1, function() {
this.$initGraph('GET', function(node) {
ok(node.canGet(), "Should be able to create Resource")
})
});
asyncTest("can remove?", 1, function() {
this.$initGraph('DELETE', function(node) {
ok(node.canRemove(), "Should be able to remove Resource")
})
});
When we execute the GraphTestCase Java class as part of the test execution, Arquillian QUnit will create and configure Drone and Graphene to represent our defined environment. It then parses the QUnit JavaScript to extract the real test names and replace the Java JUnit defined ones. That means that in our test results we’ll see test names like "can remove?" and "can get?" as opposed to "testGraph".
We have configured Drone to use the PhantomJS browser; this headless browser allows us to run on a CI server without a graphical environment. This is easily configurable via arquillian.xml.
With this setup we now have control over our JavaScript client code and can integrate JavaScript tests in our test pipeline.
We still have functional behavior in our application that goes beyond how the JavaScript code itself runs. Are the page elements displaying properly? Does the end user see what is expected?
One could argue that we’re now moving over from integration into functional testing. Either way, we need to setup our functional tests to be maintainable, robust and easy to read.
We use Drone to control the lifecycle of the browser and Graphene to wrap the browser and provide client-side object injection.
We rely on a pattern called PageObjects from Selenium to encapsulate the logic within a page in a type safe and programmable API. With Graphene we can take the Page Object concept one step further and use Page Fragments. Page Fragments are reusable components that you might find within a Page. We might have a Conference object displayed on multiple different pages or a Login controller repeated in all headers.
By encapsulating the references to the HTML ID’s and CSS rules within Page Object and Page Fragments we can create reusable Test Objects that represents our Application.
We start out by creating a Page Object for our application in org.cedj.geekseek.test.functional.ui.page.MainPage:
@Location("app/")
public class MainPage {
@FindBy(id = "action-links")
private ActionLinks actionLinks;
@FindBy(id = "user-action-links")
private ActionLinks userActionLinks;
@FindBy(id = "resource")
private WebElement resource;
public ActionLinks getActionLinks() {
return actionLinks;
}
public ActionLinks getUserActionLinks() {
return userActionLinks;
}
...
}
We use Graphene’s @Location to define the relative URL where this page can be found. By combining Graphene with Drone we may now simply inject the MainPage object into our @Test method. The injection will carry the state navigated to the correct URL and fully powered by WebDriver in the background. With this arrangement, our test class may end up with the following structure.
@RunWith(Arquillian.class)
public class MyUITest {
@Drone
private WebDriver driver;
@Test
public void testSomething(@InitialPage MainPage page) { ...}
The testSomething method accepts a MainPage object with proper state intact.
When Graphene initializes the MainPage instance for injection it scans the PageObject for @FindBy annotations to inject proxies that represent the given element. In our case we use a second layer of abstraction, ActionLinks, our PageFragment. Each page has a menu of "what can be done next?", following the flow of the underlying REST backend. These are split in two; actionLinks and userActionLinks. The differentiator: is this a general action against a Resource or an action against a resource that involves the User? An example of an action is 'Add Conference' and a User action example would be 'Add me as a Tracker to this Conference'.
We add an ActionLinks abstraction to simply expose a nicer API around checking if a link exist and how to retrieve it.
public class ActionLinks {
@Root
private WebElement root;
@FindBy(tagName = "button")
private List<WebElement> buttons;
public WebElement getLink(String name) {
for(WebElement elem : buttons) {
if(elem.getText().contains(name) && elem.isDisplayed()) {
return elem;
}
}
return null;
}
public boolean hasLink(String name) {
return getLink(name) != null;
}
}
The ActionLinks PageFragment is very similar in how the Page Object works. The main difference being the use of the @Root annotation. Both Actions and UserActions are modeled as the PageFragment type ActionLinks. They are two lists of links located in different locations on the page. In the PageObject MainPage we have the following two injection points:
@FindBy(id = "action-links")
private ActionLinks actionLinks;
@FindBy(id = "user-action-links")
private ActionLinks userActionLinks;
The ActionsLinks @Root WebElement represents the parents @FindBy element. Where on the page was this fragment found. When working within a PageFragment, all of our @FindBy expressions are relative to the @Root element.
You might remember that our application is a Single Page application, so everything happens within the same physical URL only manipulating the content via JavaScript. With this in mind we’ve modeled in a concept of a fragment being SelfAware. This allows us to encapsulate the logic of knowing how to find certain fragments within the fragment itself.
org.cedj.geekseek.test.functional.ui.page.SelfAwareFragment:
public interface SelfAwareFragment {
boolean is();
}
The MainPage PageObject implements the discovery logic like so:
public <T extends SelfAwareFragment> boolean isResource(Class<T> fragment) {
try {
return getResource(fragment).is();
} catch (NoSuchElementException e) {
return false;
}
}
public <T extends SelfAwareFragment> T getResource(Class<T> fragment) {
return PageFragmentEnricher.createPageFragment(fragment, resource);
}
Within the MainPage we want to control the creation of PageFragments so we can do it dynamically based on the requested type. This to avoid having to create a @FindBy injection point for all possible combinations within our application. But we still want our 'on demand' PageFragments to have the same features as the injected ones, so we delegate the actual creation of the instance to Graphene’s PageFragmentEnricher giving it the requested type and the @Root element we expect it be found within.
After discovering and executing ActionLinks we can now ask the MainPage: "Are we within a given 'sub page'?" by only referring to the class itself.
public static class Form implements SelfAwareFragment {
@Root
private WebElement root;
@FindBy(css = ".content.conference")
private WebElement conference;
@FindBy(tagName = "form")
private WebElement form;
@FindBy(css = "#name")
private InputComponent name;
...
@FindBy(tagName = "button")
private List<WebElement> buttons;
@Override
public boolean is() {
return conference.isDisplayed() && form.isDisplayed();
}
public Form name(String name) {
this.name.value(name);
return this;
}
public InputComponent name() {
return name;
}
...
public void submit() {
for(WebElement button : buttons) {
if(button.isDisplayed()) {
button.click();
break;
}
}
}
}
As seen in the above example in one of our SelfAwareFragment types, Conference.Form, we continue nesting PageFragment to encapsulate more behavior down the stack (mainly the InputComponent). While an HTML Form <input> tag knows how to input data, the InputComponent goes a level up.
textfield.html:
<div class="col-md-8 form-group" data-ng-class="{'has-error':error}">
<label class="control-label" for="{{id}}_field">{{name}}</label>
<input class="form-control" type="text" id="{{id}}_field" data-ng-model="field"
required placeholder="{{help}}" />
<div class="has-error" data-ng-show="error">{{error}}</div>
</div>
The complete state of the input is required. Not only where to put data, but also the defined name, "help" text and most importantly: is it in an error state after submitting?
We also have a custom extension to Drone and Arquillian; we need to ensure that "click" and "navigate" events wait for the loading of async calls before doing their time check. For this, we have the org.cedj.geekseek.test.functional.arquillian.AngularJSDroneExtension, which defines:
public static class AngularJSEventHandler extends AbstractWebDriverEventListener {
@Override
public void afterNavigateTo(String url, WebDriver driver) {
waitForLoad(driver);
}
@Override
public void afterNavigateBack(WebDriver driver) {
waitForLoad(driver);
}
@Override
public void afterNavigateForward(WebDriver driver) {
waitForLoad(driver);
}
@Override
public void afterClickOn(WebElement element, WebDriver driver) {
waitForLoad(driver);
}
private void waitForLoad(WebDriver driver) {
if(JavascriptExecutor.class.isInstance(driver)) {
JavascriptExecutor executor = (JavascriptExecutor)driver;
executor.executeAsyncScript(
"var callback = arguments[arguments.length - 1];" +
"var el = document.querySelector('body');" +
"if (window.angular) {" +
"angular.element(el).injector().get('$browser').notifyWhenNoOutstandingRequests(callback);" +
"} else {callback()}");
}
}
}
The waitForLoad method, triggered by all of the action handlers, contains the logic to wait on an async call to return.
With all the main abstractions in place, we are now free to start validating the application’s functional behavior.
*Given* the User is 'Creating a new Conference' *When* the Conference has no start/end date *Then* an error should be displayed
To satisfy these test requirements, for example we have org.cedj.geekseek.test.functional.ui.AddConferenceStory:
@RunWith(Arquillian.class)
public class AddConferenceStory {
@Drone
private WebDriver driver;
@Test @InSequence(1)
public void shouldShowErrorMessageOnMissingDatesInConferenceForm(@InitialPage MainPage page) {
ActionLinks links = page.getActionLinks();
Assert.assertTrue(
"Add Conference action should be available",
links.hasLink("conference"));
links.getLink("conference").click();
Assert.assertTrue(
"Should have been directed to Conference Form",
page.isResource(Conference.Form.class));
Conference.Form form = page.getResource(Conference.Form.class);
form
.name("Test")
.tagLine("Tag line")
.start("")
.end("")
.submit();
Assert.assertFalse("Should not display error", form.name().hasError());
Assert.assertFalse("Should not display error", form.tagLine().hasError());
Assert.assertTrue("Should display error on null input", form.start().hasError());
Assert.assertTrue("Should display error on null input", form.end().hasError());
}
The shouldShowErrorMessageOnMissingDatesInConferenceForm test method above takes the following actions:
-
Go the MainPage (as injected)
-
Get all ActionLinks
-
Verify there is an ActionLink named 'conference'
-
Click the 'conference' ActionLink
-
Verify we’re on the Conference.Form
-
Input given data in the form and submit it
-
Verify that name and tagLine input are not in error state
-
Verify that start and end input are in error state
As we can see, Arquillian Drone, together with Selenium and QUnit, makes for an integrated solution to testing front-end code with a Java object model. Running the full suite on your own locally should be instructive