If you had told me ten years ago that I would be writing Javascript professionally in 2013, I would not have believed you.

Building things in Javascript has completely changed in the last few years. Remember writing code like this and thinking it was the coolest thing you could do with your website?

function initMouseovers()
    var nav = document.getElementById('mouseovers');
    var imgs = nav.getElementsByTagName('img');
    for (var i=0;i

Thankfully, those days are long gone. Still, since JavaScript is such a flexible language, it's easy to start build spaghetti code that's a nightmare to maintain even if you are using the latest frameworks.

At HubSpot, we try hard to write our front-end code with the following mantra:

Good JavaScript code looks like good any code

Long gone are the days where a front-end hack that "just works" is good enough. The code we're writing on the front end should be just as robust and clean as the code that powers our backend jobs. That is to say, it should:

  • be DRY
  • have clear abstraction boundaries
  • have clearly labeled methods
  • ...and be testable

Which brings me to our testing environment of choice for JavaScript:

Using Jasmine with JavaScript for Testing

Here's what a Jasmine test looks like:

describe 'a cow', ->
    it 'moos', ->
        cow = new Cow expect(cow.sound).toEqual('moo')

Running the test looks like this:

Jasmine is meant to make testing easy like that. It provides a suite of matchers like .toBeDefined and .not.toMatch() to make your tests read as much like English as possible. Check out their excellent documentation for some examples. For more complex mocking capabilities (like we use to test AJAX requests without relying on actual calls to an externa API), check out Sinon.

The HubSpot Flow

We've integrated Jasmine into our core JavaScript development flow, so our developers can run their tests using the same tool they use to resolve their static dependencies, like this:

 hs-static jasmine --headless

Starting jasmine tests.

1 spec, 0 failures in 0.005s.

ConsoleReporter finished

The tests are run headlessly with PhantomJS on our build boxes, so for the first time it's now possible for us front-end people to break the build if we check in code that doesn't pass tests. Woo hoo!

Transitioning to tests

One huge hurdle in starting to build tests for an existing project is to figure out where to begin. Writing tests for things that "already work" is tedious and often brittle, especially if the code wasn't written with testability in mind.

So how are we introducing isolated tests to a huge existing codebase? Well, we start with what's easiest.

Pure Functions

This is the easiest kind of code to test in isolation: pure functions which have no side effects and no IO. For example:

UserObject --> validate() --> true/false

HTML string --> strip() --> plain text string

deeply-nested list --> flatten() --> flat copy of list

Both new and existing code which fits this description will be a snap to test in Jasmine. Your first goal is to test this kind of code only. Start with new stuff, and then add in tests to older code as it breaks.

For a great example of this kind of test, have a look at the specs for the Humanize library we recently open sourced.

Functions with one side-effect

These kinds of tests are a bit trickier, and we recommend not trying them until you have experience writing tests with pure functions. The side-effect is usually a state change which you don't want to happen when running the test.

For example:

UserObject --> inject() --> boolean

                        --> side effect from jQuery.append()

TextDocument --> save() --> updated TextDocument

                        --> side effect from jQuery.ajax()

Testing this kind of code requires a relatively straightforward invocation of a stub or mock. In both of these cases, you'll want to mock out jQuery functions.

Complex Code

Now that you know what it's like to test simple code, you can start simplifying all of your code by writing isolated tests for everything. At this point you will know to think twice when you start writing a test which:

  • Requires more than three mocks
  • Has to check the DOM for changes
  • Is too long
Code that's easy for a test suite to understand will be easy for a human to understand. Can't rewrite a particular piece of code? Then isolated tests aren't a good idea.

Unit testing is one of many software development best practices which are just now making a real appearance in JavaScript development.

The transition to building tests (or moving to a TDD flow) can be bumpy, but it improves the structure of your code, provides "free" documentation for other developers who use your code, and removes that element of fear you feel when others are depending on your code and it's time to refactor.

So get in there and test your code! 

Recommended Articles

Join our subscribers

Sign up here and we'll keep you updated on the latest in product, UX, and engineering from HubSpot.

Subscribe to the newsletter