Recently I’ve been working on some utility libraries to help abstract out common ‘map management’ functionality from our core Marionette map module. (for more info on using Backbone & Marionette with the Esri javascript api, see my previous series of posts on the topic)
Without getting deep into the weeds on what I’m doing, suffice to say we are setting up the state of the map, based on some json configuration info a common pattern shown here . This configuration can specify all sorts of things, so in order to have confidence in my code, I wanted to drive the development through tests.
Basic Setup
For this, I’m using a vanilla text editor (SublimeText2 if you were wondering) along with grunt. Essentially, I use grunt to do many of the things that I’d use an IDE for in the past automated code linting, doc generation, and running unit tests. Grunt is based on node.js, and while I’m not going to get into setting up grunt, the Getting Started page at gruntjs.com will get you up and running.
For unit testing, we have been using Jasmine which is a BDD-style framework for testing javascript. We use the grunt-contrib-jasmine task to run the test specs in phantomjs (essentially chrome, that runs without a visible window aka headless). We use a watch task to have the unit tests run every time a file is saved. Sweet.
Another nice thing with Jasmine is the jasmine-jquery plug-in. This helper library makes is really easy to work with jquery both to use jquery syntax to check DOM state, and to mock jquery ajax calls. Additionally it brings in support for fixtures, which are bits of css/html/json which can be pulled in by your tests, but are stored cleanly off in separate files.
I’ll walk through how this all works, link to the github repo with the sample code and explain how to get up and running with the code.
How Jasmine Works
I thought I’d throw in a quick description of how the tests are run because it will help explain what I did further down.
Essentially the test runner creates a HTML page, injects your dependencies in (jquery, backbone, whatever), followed by the jasmine runner, the test specs themselves and finally some runner code to kick the whole thing off. Jasmine simply executes the specs, and reports success/fail of your expectations.
One thing we need to add to this HTML page is links to the Esri JS API itself, as well as links to the css files so the map will correctly render. To do this, we tell jasmine to use a custom template for the runner (the options.template item in the jasmine configuration in gruntfile.js). This is simply an underscore template that is used to create the test runner, and I simply added in the script and css files as shown here.
There is a lot more to writing specs etc etc, but at a high-level it’s code in a browser which makes sense since we are trying to test client-side code. Read all the details about configuring the jasmine task at https://github.com/gruntjs/grunt-contrib-jasmine and details on Jasmine itself at http://pivotal.github.io/jasmine/.
Using Fixtures to Load Test Data
In our tests we want to send in lots of different types of data good data, bad data, mixed good and bad etc. As I mentioned, Jasmine-Jquery adds in the option to load up html, css, and json fixtures from files. This is really nice because the test data lives right with the code, is versioned with the code, and is pulled down with the code. Ergo, the bar is low for you and your team to keep running the unit tests.
Here is an example of one of these files that’s used in the test shown lower down
Testing with the JS API
Ok so let’s get to the heart of the matter here we want to test code which changes the state of the map. How do we do this?
One super labor intensive option would be to create a full mock of the Esri JS API because our goal here is to test our code, NOT that the JS API. However, this would be an enormous task, so next best option is to actually use the JS API in our tests.
We want our tests to run independently, so each test really needs it’s own map instance. Jasmine has facilities allowing onBeforeEach and onAfterEash functions to be setup, and while that would appear to be the right place to create & destroy our map, it does not work as you’d expect, because creating a map is async, and onBeforeEach does not wait for callbacks before it starts working on your test logic.
The real trick here is to leverage Jasmine’s async support mechanism to create the map in the DOM, and then start making your assertions.
Jasmine Async Testing
To test normal asycn operations, Jasmine uses a structure that’s based on callbacks and a timeout.
Let’s look at some code:
So this is our basic structure to work with. What I ended up doing is nesting a second level of callbacks in the first run call. The first level is a call to a helper that simply creates a div in the test harness page, and then instantiates a map in that div. In that code, I listen for the onLoad event (aka ‘load’ if you’re an ‘on’ style eventer), and then issue the call to the callback that was passed in.
The nesting I was talking about is that the callback passed to this helper is actually the call we want to test, and it also has it’s own callback.
Let’s look at the helper:
Ok, that’s not so bad right? Now lets look at a test that is using it…
Debugging
Ah yes… debugging is tricky when dealing with a headless browser! So there are two main ways I’ve been working with this. The first is to use console.info(‘some important info’) as this will get printed to the screen by the grunt runner. It’s nasty but it can be helpful.
The second option is to set keepRunner: true in your grunt file (I have set this in the example code). What this does it allows the actual runner to persist between test runs. By default, the grunt-contrib-jasmine task will remove the file, but if we keep it around, we can use it… for debugging. To do this, just drop a debugger statement in your code, then load up the spec runner file (_SpecRunner.html) and step through things.
NOTE: Here is a gotcha that had me stumped for a while since this runner (_SpecRunner.html) will be loaded as a file, and chrome (by default) does not allow ajax calls from a file:// to a file:// the fixtures will no load! There is a flag you can set to allow chrome to do this, but that’s a bit of a security risk, so just use FireFox where this is not an issue.
Get the Code
I whipped up a very simple project to show how to use this it’s over at https://github.com/dbouwman/jsapi-jasmine please fork and go crazy with this. And if you have a better / cleaner way to do things, by all means send pull requests.
Getting it Running
For this not familiar with this tool chain, the list of things being used can seem daunting. But no fear node has you covered!
First, make sure you have node it runs on windows just as well as on a Mac, so no fear there. If you don’t have it installed, pop over to nodejs.org and get it.
Then, from a terminal / command window, cd into the folder where you pulled down the code, and simply issue npm install. If you are on a *nix system, you may need to sudo npm install.
With that out of the way, run grunt watch in the project folder, and then edit any of the javascript files and watch your tests run in the terminal. It should look something like this:
Hope this helps you get up to speed testing your Esri JS API applications.