Writing better 3rd party Javascript with Coffeescript, Jasmine, PhantomJS and Dependence.js

Josh Carver - 05 Oct 2011

Here at Bizo we recently underwent a major change to our Javascript tags that’s used in our free analytics product (http://www.bizo.com/marketer/audience_analytics). Our code gets loaded by millions of visitors each month across thousands of web sites; so our Javascript has to run reliably in just about any browser on any page.

The Old Javascript:

Unfortunately our codebase had accumulated about three years of cruft, resulting in a single monolithic Javascript file. The file contained a single closure, over 600 lines long, with all kinds of edge cases, some of which no longer existed. Since you can’t access anything in a closure from the outside, writing unit tests was nearly impossible and the code had suffered as a result.

That’s not to say we had no tests – there were several selenium tests that tested functionality at a high level. The problem however was making the required changes was going to be a time consuming (and somewhat terrifying) process. The selenium tests provided a very slow testing feedback loop and debugging a large closure comes with it’s own challenges. If it’s scary changing your production code, then you’re doing something wrong.

Modularity and Dependency Management

So we decided to do a complete overhaul and rewrite our Javascript tags in CoffeeScript (smaller code base with clearer code). The biggest problem the original code had was that it wasn’t modular and thus difficult to test. Ideally we wanted to split our project into multiple files that we could unit test. To do this we needed some kind of dependency management system for Javascript, which in 2011 surprisingly isn’t standardized yet. We looked at several projects but none of them really met our needs. Our users are quite sensitive about the number of http requests 3rd party Javacript makes so solutions that load several scripts in parallel weren’t an option (ex. Requirejs). Others like Sprockets were close but didn’t quite support everything we needed.

We ended up writing Dependence.js, a gem to manage our Javascript dependencies. Dependence will compile all your files in a module into a single file. Features include javascript and/or Coffeescript compilation, dependency resolution (via a topological sort your dependency graph), allowing you to use an “exports” object for your modules interface, and optional compression using Google’s Closure compiler. Check it out on github:


Fast Unit testing with Phantom.js

Another way we were looking to improve our Javascript setup was to have a comprehensive suite of unit tests. After looking at several possibilities we settled on using the Jasmine test framework (http://pivotal.github.com/jasmine/) in conjunction with PhantomJS (a headless webkit browser). So far using Jasmine and PhantomJs together has been awesome. As our Javascript is inherently DOM coupled, each of our unit tests executes in a separate iframe (so each test has its own separate document object). 126 unit tests later the entire suite runs locally in about 0.1 seconds!

Functional Testing with Selenium

Our functional tests are still executed with Selenium webdriver. Although there are alternative options such as HtmlUnit, we wanted to test our code in real browsers and for this Selenium is still the best option around. A combination of capybara and rspec make for writing functional tests with a nicer api than then raw selenium. A bonus is that capybara allows you to swap out selenium in favor of another driver should we ever want to switch to something else. Lastly a custom gem for creating static html fixtures allows us to programmatically generate test pages for each possible configuration option found in our Javascript module. You can find that here:


Wrapping up

The new code is far more modular, comprehensively tested and way easier to extend. Overall working with Dependence.js, CoffeeScript, PhantomJs, Capybara, Rspec and Selenium has been a workflow that works great for us. If you have a different workflow that you like for Javascript projects, let us know!

comments powered by Disqus