Interacting with the user interface

A great deal of the JavaScript in use today is used on the client and is used to interact with elements that are visible on the screen. Interacting with the page flows through a model of the page known as Document Object Model (DOM).

Every element on the page is represented in the DOM. Whenever a change is made to the page, the DOM is updated. If we add a paragraph to the page, then a paragraph is added to the DOM. Thus if our JavaScript code adds a paragraph, checking that it does so is simply a function of checking the DOM.

Unfortunately, this requires that a DOM actually exists and that it is formed in the same way that it is on the actual page. There are a number of approaches to testing against a page.

Browser testing

The most naïve approach is to simply automate the browser. There are a few projects out there that can help with this task. One can either automate a fully-fledged browser such as Firefox, Internet Explorer, or Chrome, or one can pick a browser that is headless. The fully-fledged browser approach requires that a browser be installed on the test machine and that the machine be running in a mode that has a desktop available.

Many Unix-based build servers will not have been set up to show a desktop as it isn't needed for most build tasks. Even if your build machine is a Windows one, the build account frequently runs in a mode that has no ability to open a window. Tests using full browsers also have a tendency to break, to my mind. Subtle timing issues crop up and tests are easily interrupted by unexpected changes to the browser. It is a frequent occurrence that manual intervention will be required to unstick a browser that has ended up in an incorrect state.

Fortunately, efforts have been made to decouple the graphical portions of a web browser from the DOM and JavaScript. For Chrome this initiative has resulted in PhantomJS and for Firefox SlimerJS.

Typically, the sorts of test that require a full browser require some navigation of the browser across several pages. This is provided for in the headless browsers through an API. I tend to think of tests at this scale as integration tests rather than unit tests.

A typical test using PhantomJS and the CasperJS library that sits on top of the browser might look like the following:

var casper = require('casper').create();
casper.start('http://google.com', function() {
  assert.false($("#gbqfq").attr("aria-haspopup"));
  $("#gbqfq").val("redis");
  assert.true($("#gbqfq").attr("aria-haspopup"));
});

This would test that entering a value into the search box on Google changes the aria-haspopup property from false to true.

Testing things this way puts a great deal of reliance on the DOM not changing too radically. Depending on the selectors used to find elements on the page, a simple change to the style of the page could break every test. I like to keep tests of this sort away from the look of that page by never using CSS properties to select elements. Instead make use of IDs or, better yet, data-* attributes. We don't necessarily have the luxury of that when it comes to testing existing pages but certainly for new pages it is a good plan.

Faking the DOM

Much of the time, we don't need a full page DOM to perform our tests. The page elements we need to test are part of a section on the page instead of the entire page. A number of initiatives exist that allow for the creation of a chunk of the document in pure JavaScript. jsdom for instance is a method for injecting a string of HTML and receiving back a fake window.

In this example, modified slightly from their README, they create some HTML elements, load JavaScript, and test that it returns correctly:

var jsdom = require("jsdom");
jsdom.env( '<p><a class="the-link" ref="https://github.com/tmpvar/jsdom">jsdom!</a></p>',["http://code.jquery.com/jquery.js"],
  function (errors, window) {
    assert.equal(window.$("a.the-link").text(), "jsdom!");
  }
);

If your JavaScript is focused on a small section of the page, perhaps you're building custom controls or web components, then this is an ideal approach.

Wrapping the manipulation

The final approach to dealing with graphical JavaScript is to stop interacting directly with elements on the page. This is the approach that many of the more popular JavaScript frameworks of today use. One simply updates a JavaScript model and this model then updates the page through the use of some sort of MV* pattern. We looked at this approach in some detail some chapters ago.

Testing in this case becomes quite easy. Our complicated JavaScript can simply be tested by building a model state prior to running the code and then testing to see if the model state after running the code is as we expect.

As an example we could have a model that looks like the following:

class PageModel{
  titleVisible: boolean;
  users: Array<User>;
}

The test code for it might look as simple as the following:

var model = new PageModel();
model.titleVisible = false;
var controller = new UserListPageController(model);
controller.AddUser(new User());
assert.true(model.titleVisible);

As everything on the page is manipulated, through the bindings to the model, we can be confident that changes in the model are correctly updating the page.

Some would argue that we've simply shifted the problem. Now the only place for errors is if the binding between the HTML and the model is incorrect. So we also need to test if we have bindings correctly applied to the HTML. This falls to higher-level testing that can be done more simply. We can cover far more with a higher-level test than with a lower-level one, although at the cost of knowing exactly where the error occurred.

You're never going to be able to test everything about an application but the smaller you can make the untested surface, the better.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.85.221