I’ve gotten very satisfied with the set of tools I’m currently using when
writing JS tests, so much so that I’d thought I’d write a blog post about it.
I write my tests either using Chai and Sinon
or Jasmine. I also use a forked
version of JSCovReporter to show me code coverage information.
Whilst writing and
developing the tests I’d either run them in a browser (and manually refresh) or via phantomjs.
This means that most of the time my tests are being tested in either firefox or
a webkit-based browser. Of course before committing, the tests are checked manually in all browsers in case of cross browser issues.
This last step though is now replaced with a tool called testem.
Testem is a command line tool that automates the running of your tests in any specified
useragents and displays the results in the terminal. It’ll detect changes in the
test files and automatically reruns the tests. It can also run the tests in CI
mode which means the results are output in TAP format from which Jenkins can parse and
So now, instead of having a browser open whilst writing tests, I run a command like
[testem simple usage]
$ testem -f testem.json
and windows for Chrome, Safari and Firefox are opened, the tests are run and results
are displayed (The browsers and tests page(s) are specifed in the testem.json
file). Alternatively you can also run it headlessly using phantomjs if you don’t need a browser.
The results looks like this:
The browser windows are kept open so subsequent changes will trigger the re-run
of the tests. Though, in this example, only the above browsers were specified, I
can add other browsers manually just by opening the same url in a different browser.
At work for instance, where I work on an iMac with 8gigs of memory and have
access to multiple mobile test devices, I would also have tabs for all the IE’s running via
a vm, iOS simulator and a couple of android phones. So now, rather than wait for Jenkins
or perform a quick manual check of the tests in each browser before committing,
I know pretty much straight away if a test fails because of a cross-browser issue.
The icing on the cake is that only a small change to your spec runner files
[Add testem support to your test runner page]
It’s a bit ugly but the conditional is there so there the test page can be
run manually without testem.
Running testem in CI mode is done using the “ci” parameter:
[testem simple usage]
$ testem -f testem.json ci -b Chrome
The results are displayed in TAP format which can be piped to a file which Jenkins
can be configured to read from. Full configuration of Jenkins can be found on the
testem use with jenkins
I use a forked version of JSCovReporter to generate the coverage report. It’s forked
to remove the dependency on backbone.
In order for JSCovReporter to render the report the tests need to be run against
pre-instrumented code so when each statement in the files are run they can be counted.
This is done using CoverJS like so:
[testem simple usage]
$ coverjs ./*.js -o ./instrumented
The above command instruments the specified files and outputs them into an directory
called instrumented. These files should then be included in the test page in order
for the code coverage report to be generated correctly.
The test page for my very small micro lib preposterous uses both testem and JSCovReporter
so should serve as a simple integrated example.
The preposterous project page is a autogenerated composite
page of the latest version of the project Readme and chaijs test report. There is also a coverage page
that includes the code coverage report (click on the file name on the right or scroll down to see the full report).
The source code for both the tests reports (minus the project Readme) can be found in the test directory
and shows how to integrate both testem and JSCovReporter into a test runner page.
I’ve been playing with Rhino. Specifically, reading in json objects, turning that into normal, runnable objects and saving that to a file so browsers can run it. More specifically, the code that transforms the JSON to an object in the browser should be exactly the same as the one that runs in Rhino. That way I don’t have to maintain two different versions.
I’m working on an idea that requires reading in JSON objects and creating objects with methods and properties based on that JSON and could be normally done per request of the page. Obviously, if the JSON is pretty much static, it would be best if we could save the resulting object to a file and reference that file in our pages instead. It would save 1) the request for the JSON file and 2) processing to create the final object. There are two steps to do this.
Transform the JSON
Saving the object
Transform the JSON
Easy, enough. Iterate through the JSON object, create new object and add methods and properties to it as you see fit. To save it though you need a String representation of the object. Gecko’s toSource() method helps there.
Saving the object
In Rhino, make use of Java’s FileWriter object and save it to the filesystem. If your object is really simple then that’s probably it and you’ll be able to run the object in your browser. If your final object makes use of closures then you’ll get errors, obviously, as the variables the closures have reference to, aren’t being created at all when the code is run in the browser. Solution (when run in Rhino) is to turn those methods that use closures into strings and add the variables in via regexp or whatever and then eval it!! Remember, it’s only done in Rhino, so it’s only done once. One thing to note, you don’t have to replace every occurence of that variable name, only the first one (or add a initialisation statement for that variable to the function). Now when the code is written out to file (and read back in by the browser) the variables will have a valid value.
For simple types, like String or numbers, this will work fine. For objects or arrays, you could use toSource() but that just dumps out a object literal of the object at that time and not a reference which is what you really need. The other thing is you’ll have a large object dump in the final code which makes the final code much much larger than you need. The solution then is store these objects in an Array or manager object (outside this system) and retrieve them as needed within your final code.
Here’s an example:
There you go. Ugly, more than probably not robust but it’s the only way I’ve found to do what I need to do for my use case; admittedly probably not a common usage scenario.
With web apps becoming more and more like Desktop apps, it seems like user interfaces need be more like desktop user interfaces too. Some of these web apps can have a complex UI and their users need to be able to feel comfortable with it. Of course, making the UI as simple as possible is key, but if a task is relatively complex then the UI will also be relatively complex. With more complex tasks, users can make mistakes or want to change things and if they do, they’d appreciate a way of doing so. One way to do that is to provide the user a way of undoing their actions; a way to retreat over their history. Just to make clear, this is something along the lines of a user interaction history, not a browser navigation history. Hence ActsAsUndoable, one way of adding undo functionality to your interactive widgets. (Incidentally while I was writing this post, Aza Raskin posted an article on A List Apart about using undo functionality in interaction design, which seems to dovetail well with this post.)
Basically the Multi-Level Undo pattern says that if users can navigate through their action history and undo their actions, then they can explore their own work paths quickly and safely. The most obvious desktop example of a navigable history is Photoshop’s history panel.
The pattern states that these kind of actions should be undoable:
Text entry for documents or spreadsheets
Modifications to images or painting canvases
Layout changes — position, size, stacking order, or grouping in graphic applications
File operations, such as deleting or modifying files
Creation, deletion or rearrangement of objects such as email messages or spreadsheet columns
Any cut, copy, or paste operation
There are apps like these available on the web and some do have some simple undo/history functionality. These do tend to be only a single step undo and then, only for actions that change and save a new state eg move to trash. But also other actions that users make but perhaps don’t yet want (or need) to save should also be undoable and at a multiple level too. So, how do we as implement something like the Multi Level Undo interaction pattern? We use the Mementosoftware design pattern.
Two of Jennifer’s examples are text entry and stacking order changes. I thought these would be relatively simple examples to use for demo purposes. Here’s the text entry demo and also the stacking order demo. I used the demo of the YUI’s sortable list which I hope they don’t mind. If you want, take a look at these to see whats going on before reading on.
The first example I’ll cover is the text entry one. It’s a simple example of a textarea in which users can enter text and save snaphots. The original class has two properties el and sValue. el represents the textarea and sValue, the textarea’s value. It has a method called setText() whichs sets sValue to the value of the textarea.
Finally we have a snapshotText() method which calls setText(). We wire a button element to this method.
We can add undoable functionality by augmenting it with YAHOO.Acts.as.Undoable. This adds a sCaretakerGroup property and undo(),redo(),revert() and restore() methods. All we need to do is subscribe to the ‘stateLoaded’ event and make a call to saveState() of the Caretaker to record the initial state. We can do this in the constructor of out TextUpdate object or a init() method if we choose to use one.
The first line just sets up a property that allows us to identify a caretaker to manage our states (more on this later).
The second line subscribes to an event called ‘stateLoaded’ and sets the callback to our restore() method.
Then, the third line saves our initial state. We need to do this so the revert() method can revert to the initial state. The callback we specify here depends on your object
So now our TextUpdate class looks like this :
The last line adds the undoable functionality to the TextUpdate object. (see next code snippet)
But what’s all this caretaker stuff? Well, the Memento software design pattern is an established pattern that helps to implement undo functionality. I touched on the Memento pattern in an earlier post about object-oriented Actionscript.
The Memento pattern has three classes; Originator, Caretaker and Memento. Basically using these three classes, the Memento pattern works likes this:
If an Originator wants its state or some of its state to be undoable or redoable, then it must save its state to a Caretaker. This Caretaker will manage the various states (Mementos) of our Originator. (isn’t it lovely terminology?)
Using this as a premise, we can devise a simple ‘framework’ for adding multi level undo functionality to our apps. We don’t need (or want) to create lots of class hierarchy for our apps but the only classes we need to create revolve around the Caretaker object. Each object that we need undo functionality for, we have a corresponding caretaker. To manage multiple Caretaker objects (since perhaps multiple objects need a separate history), we use a CaretakerRegistry singleton object to do so. This allows Caretaker objects to be registered and created, if not done so already. It also acts as proxy to caretaker objects as all calls to Caretakers are made via the CaretakerRegistry. A registry also allows multiple objects to monitor state changes of an Originator. See the HistoryList section for an example of such an object
Interaction is achieved using CustomEvents. Caretaker objects fire ‘stateLoaded’ and ‘stateSaved’ events. Originator objects subscribe to the ‘stateLoaded event. Any other object that is interested in the state of our Originator will subscribe to both ‘stateLoaded’ and ‘stateSaved’ events. Each event passes the Memento object to each listening object.
Everytime the state of our object has changed and needs to be saved, we call the saveState method (see line 19) of our object’s Caretaker which makes a note of the new state and fires off a ‘stateSaved’ event.
Here are the methods that ActsAsUndoable adds.
Our object also has undo(),redo(),revert() methods, all of which call the loadState() method of the Caretaker. This, in turn, fires off a ‘stateLoaded’ event. Because our originating object subscribes to the ‘stateLoaded’ event it is passed the Memento object, which represents the state of the object that it needs to be undone, redone or reverted to. The listening method of our object that the custom event fires to is called restore(). This is the method that sets the state of our object. The restore() method is passed an object with three properties. These are args,execute, and oScope and are defined when we save the state via the saveState() method. Our restore() simply calls the method specified in execute passing as args those specified in args using oScope as the scope for the method.. I restore the state of the object like this (via a method call) rather than simply overwriting some properties with older properties as often some logic needs to be run as well; just resetting the properties probably won’t suffice in anything other than a very simple widget). The best way to do that is via existing methods on our originating object. For the pattern heads out there, this way is similar to the command pattern without actually creating Command objects – the existing methods are the Commands.
The stacking order demo is similar. I rewrote the example to reflect the changes needed to make the reordering done by single method (orderLi). Also I haven’t augmented it with ActsAsUndoable as I don’t use the undo, redo or revert functionality in the UI. The source for YAHOO.example.DDApp is probably a good example to view if you want to see how to add undoable functionality without using augmentation.
The History List object is one that listens to state changes of a given object and renders them in a list. It shows how other objects besides the Originator can monitor changes of state. Clicking on each item in the list, rolls back the state of that object to the state. You just initialise it with the id of the container and the CaretakerGroup that the object you’re providing a history for is using.
Each link in the history list is a named anchor. However in the update() method, we stop the Event so the browser doesn’t actually add it to its own history. We don’t want the act of navigating through the history of our own actions within our task to interfere with the history of our browser.
We can create a new HistoryList object that monitors changes to our object by this :
The first is the id of an element in which to render the history list. The second is the name of the caretaker group to monitor. The list is rendered to the document and any saves, reverts, undo etc are shown in this list.
Resources and notes :
The ActsAsUndoable was an interim tongue-in-cheek working name but I kinda like it. The Acts.as namespace is a bit overkill but it fits pretty well and will work out nicely in future things I want to post about.
For the textUpdate example I’ve used the word ‘snapshot’ rather than ‘save’ as ‘save’ would probably mean save permanently to most people. Another button called save could easily be added that would do a true save via normal form functionality. It could even be done using AJAX and the Browser History manager from the YUI library, which would add an item to the history list. Having said that, we don’t want to confuse user with two histories; browser history and what I call User Interaction History.
I used the terms as specified as in the Memento Design Pattern. These aren’t the clearest names and I was in two minds of calling Caretaker and CaretakerRegistry, UserActionManager and UserActionManagerRegistry. But I suppose these objects can be used for things other than user actions so I kept the design pattern language.
You can download the examples and js files. One of these days I’ll make my subversion repo public but for now old fashioned zip files will have to do.
Finally, thanks to Tony Kabalan for being a sound board and inputting some useful ideas.