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?
// THIS IS GONNA BE SO SWEET FOLKS 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:
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:
Which brings me to our testing environment of choice for JavaScript:
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.
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. Finished ----------------- 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!
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.
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.
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:
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!