the two alexs

Test your Javascript!

In  this post, I’ll discuss the whys and hows of Javascript testing; briefly discuss Jasmine, a testing framework; and review some of potential challenges in testing Javascript, which I’ll discuss in more detail in several follow-up posts.

I like Javascript.  The language has some great features that make it possible (even fun) to build powerful, interesting programs; as a programmer, I find few things as rewarding as using the language to create a dynamic UI.  Of course, it’s no secret that Javascript also comes with unhappy features (some from the browsers, others from the language itself) that make using the language tricky and error-prone.  The more Javascript you use, the more likely you are to spend hours painfully debugging some obscure issue in IE7.

There are a few solutions to this dilemma:

  • You could just avoid Javascript — stick instead to simple Ajax calls and let the server handle the rest.  (Unfortunately, that’s a little like cutting off your web browser’s left hand.)
  • You could just write your code and fix problems as they come up.  Why worry now?   (Because you’ll worry later, possibly late one Saturday night night.)
  • You can test your Javascript.  More work upfront, fewer problems down the road.

After a painfully long time firefighting Javascript issues, I’ve come to wholeheartedly embrace testing.  That’s hardly a controversial position, I admit, but it’s one that doesn’t seem as widespread in the Javascript community the way it has for other languages.

Since I’ve spent the last few months writing a few thousand lines of test code, it seems like a good time to pull my thoughts together and share what I’ve learned.  Consider it my holiday gift to all of you :)

The Why

So why test your Javascript?  Well, an easy answers springs to mind:

  1. Because all your code should be tested, duh. Your Javascript is just as legit as your Ruby or your Python or your PHP.  Of course it should have tests.

You don’t have take it only from me that testing is relevant to Javascript: JS ninja John Resig, creator of jQuery, chose to put test frameworks first, before all other subjects, in his excellent new book Secrets of the Javascript Ninja.  He clearly thinks it’s important.

However, I’ll go beyond that one (strong) argument.  I think that testing Javascript code may be even more urgent than testing server-side code, for two reasons:

  1. Because Javascript is front-end. Errors in Javascript are particularly visible and annoying to your users.  Think of it like our old friend the Uncanny Valley — users will more easily accept an obviously broken site (an error page) than a site that looks normal but mysteriously doesn’t work (a form whose Javascript validation is broken).  I don’t have any data to back that up, but I know which one I find more irritating.
  2. Because debugging Javascript is a pian in the ass. If you’ve ever had to reproduce and debug an intermittent problem seen by users on IE, you know what I mean.  Even at their best, Javascript debugging tools aren’t as good as their server-side equivalents; at their worse, it’s just you and alert against the world.  Anything you can do to avoid that fate seems worth it.

Let’s get concrete

Let’s take a quick look at three real-world cases in which testing Javascript has saved me from trouble:

  • Browser differences: my tests recently turned up two cases in which I used a native method (Object.create) not provided in older environments; I also discovered oddities in IE8′s console.log (it doesn’t support .apply()).  Being able to run a test suite on multiple browsers makes it easy to find, reproduce, and solve such problems without spending unnecessary time in IE-land.
  • Corner cases: for an image uploading library, I wrote several (non-deterministic) test cases to smoke out corner cases — for instance, simulating an arbitrary number of uploads that randomly either finished, were canceled, or errored.  These tests were invaluable, unearthing several rare problems that might have been impossible to solve from user reports alone.
  • Code structure: just this month, test-writing helped me identify an overgrown initializer whose expanding DOM setup section warranted its own function.  Though originally spurred by my need to test each part independently, splitting that method has improved the overall quality and maintainability of my code — an example of how tests can lead to better software even before the first test case is run.

Onward!

Now that we’ve talked about why we should be testing, let’s turn those newfound intentions into error-finding assertions.

The How

Last spring I wrote my first tests in JSUnit, a now-defunct port of JUnit to Javascript.  This fall, I went looking for a more modern, better maintained testing framework, and discovered that there are a number of excellent Javascript testing frameworks, including QUnit, used by jQuery and Facebook and YUI Test, from Yahoo.  Many provide familiar features, such as modular test groups and assertion suites; most also offer a browser-based test runner and can be integrated into automated testing.

I ultimately chose Jasmine, a project from Pivotal Labs.  It supports all those features (modular testing, assertions, browser UI, integration into continuous testing), and offers two additional features that won me over:

  • Mocking: Jasmine provides a built-in mocking framework, allowing you to intercept function calls, guarantee their return values, and write expectations against them.  It’s hard to overstate the value of mocking in writing reproducible test cases; it’s the main reason I chose Jasmine.  I’ll provide some examples of mocking below.
  • Syntax: Jasmine uses an RSpec-like syntax, which I find easy and descriptive.  While QUnit breaks modules apart by calling a method between test cases, and YUI Test uses constructors and hashes to define test groups, Jasmine tests follow an easy-to-read structure:
    describe("My Test suite", function() {
      it("should test something", function() {
        // mocks and expectations
      })
    
      describe("a testing subgroup", function() {
        // more tests
      })
    })

After writing over two thousand lines of tests with Jasmine, I’ve found it lives more than meets my expectations.  (Ha!)  So let’s take a look at how it works.

Using Jasmine

If you’ve written tests before (or even if not), Jasmine should be pretty straightforward.  Let’s take a quick look at how Jasmine works — after that, you should check out Pivotal’s documentation, which also covers how to set up a test environment and run the tests in your browser.

describe("Jasmine", function() {
  it("makes testing JavaScript awesome!", function() {
    expect(yourCode).toBe("lots better");
    expect(bugLevel).not.toBe("high");
  });
});

Here, we see all the basic testing ingredients:

  • Test modules: tests are grouped in describe blocks, which can be nested indefinitely deep inside each other.  Each block can have its own setup and teardown methods (see below).
  • Test cases: tests are registered by the it() method, which takes a description and a function containing the test.
  • Assertions: assertions in Jasine come in two parts — expect() and an assertion function, such as toBe().  expect sets up which object or value you’re about to make an assertion about — it can be a variable, raw value, or even a function (see mocking below).  Assertions can be negated by adding .not between the expect and the assertion (as above).

Setup and Teardown

We can also set up setup and teardown functions (beforeEach and afterEach) at any level.  This is actually quite powerful, allowing you to organize test groups with successive levels of specificity (tests for a given method, tests for that method if the interface isn’t yet loaded, etc.):

describe("ImageUploader", function() {
  var imgUploader;
  beforeEach(function() {
    // clean copy each time
    imgUploader = new ImageUploader();
  })

  it("should define a property", function() {
    expect(imgUploader.myProperty).toBeDefined();
  })

  describe("once intialized", function() {
    beforeEach(function() {
      imgUploader.init();
    })

    it("should contain some value", function() {
      expect(imgUploader.anArray).toContain(someValue);
    })
  })
})

Mocks

Mocking is awesome.  In Jasmine, mocks are called spies — you spyOn a function, and can then fake its result and see if it was called.  A few examples:

describe("ImageUploader", function() {
  it("should act a certain way if an image is uploading", function() {
    // easily guarantee you're test runs with the expected conditions
    spyOn(imgUploader, "isUploading").andReturn(true);
    imgUploader.myMethod();
    expect(imgUploader.myProperty).toEqual(someValue);
  })

  describe("when intializing", function() {
    it("should notify the upload manager", function() {
<span style="font-size: 11.8056px;">      // this method will be invoked in init</span>
      spyOn(uploadManager, "register");
      imgUploader.init();
      // validate that a method was invoked as expected
      expect(uploadManager.register).toHaveBeenCalledWith(imgUploader);
    })

    it("shouldn't fire an event if the page is still loading", function() {
      // another method used in init
      spyOn(Page, "isLoaded").andReturn(false);
      spyOn(imgUploader.node, "trigger"); // jQuery trigger function
      imgUploader.init();
      // make sure a function wasn't called
      expect(imgUploader.node.trigger).not.toHaveBeenCalled();
    })
  })
})

Using mocks to control your testing environment is much, much easier than rigging the right conditions each time.  If you’ve never used mocks, you should definitely give them a try.

Gotcha for Rubyists: in Jasmine, the order of method calls is different than in RSpec.  As in Ruby, you set up your spy behavior first, but you call  call expect after executing the relevant code to be tested.

Custom assertions

You can create custom assertions (called matchers) to handle common tests in your code.  It’s fairly simple task — I’ll cover how to write custom matchers, with useful examples, in a follow-up post.

Real code

Want to see some real Jasmine tests?  Just take a look at the test cases I’ve written for some utility functions in my image uploading library.  (If that makes sense, there are a number of other, more complicated tests in the same repository.)

Challenges

This being Javascript, there are some challenges you’re likely to encounter as you write your tests.  Some are self-inflicted, others come from the way the language works.  In particular, I’m going to cover four areas worthy of their own follow-up posts:

  1. Developing reusable test cases: basic test blocks are easy, but you can quickly run into Javascript scoping issues if you want to build dynamic tests.
  2. Testing against browser events: browsers, of course, all behave differently, and you have to do some quick footwork if you want to test code that relies on events. 
  3. Testing encapsulated methods: encapsulation (defining variables in a private scope) is a powerful technique, but it doesn’t sit perfectly well with testing.  I don’t have a surefire solution to this problem, but will discuss my attempts to find a balance.
  4. Overmocking: it’s deceptively easy (I say from experience) to start testing the implementation itself rather than the results, making your tests more brittle.  Avoiding overmocking is mainly a matter of awareness, so I’ll present several examples from my own code.

Keep an eye out for those posts early next year!

Conclusion

That’s Javascript testing in a very brief nutshell.  If it saves even one reader from the pain of an emergency debugging date with IE6, it’s been worth it.

As always, I’d be happy to answer any questions and would love to see your comments.

Hope you’ve all had happy holidays so far — have a fun, safe, and bug-free New Year!

Alex

Koala 0.10 released!

I’m excited to announce the release of Koala 0.10!

New Features

In this release, we’ve added support for Facebook’s new Test User API — huge thanks to Rafi Jacobi, my colleague at Context Optional, for contributing this feature. With the TestUsers API class you can create individual test user accounts:

test_users = Koala::Facebook::TestUsers.new(app_access_token)
# returns the test user's information
test_users.create(is_app_installed, permissions)

You can also make friend connections between your test users, delete them, and so on. You can also create a whole network of test users who are friends with each other using our create_network method. Of course, everything is tested (though the live tests for test user networks take a long time) — all this and more is documented in the Koala wiki.

With 0.10, we’ve also made the access_token property accessible (read-only) and fixed a minor bug.

Upcoming Release

Chris and I are working on support for image and video uploads. We’ve had several requests for this and are eager to add this last feature and hit 1.0, and hope to have it done early next year. (Since neither Net::HTTP nor Typhoeus natively support multipart POST uploads, it’s slightly more complicated than most features.) Stay tuned!

Happy holidays,

Alex

Facebook and Ruby (a presentation)

I gave a presentation on Facbook to the Ruby Users Group here in Munich yesterday.  I’m posting it here in the hopes it’ll be interesting and useful to the community in general.  (This was my second presentation to a Ruby group this year — I gave one on metapgrogramming in July in San Francisco — and I think I’m starting to like presenting.)

In the presentation, I cover the basics of developing web applications with Facebook — what Facebook integration can mean, how the Facebook APIs work, what they can do, and how to use them.  This being a Ruby group and me being the author of the Koala gem for Facebook, I provided some examples of querying the Social Graph using a few lines of Ruby code; I also covered Facebook’s Javascript SDK briefly, since that’s critical to significant Facebook application.

Oh, and I gave the talk in German (don’t worry, the slides are in English).  That was a good challenge.

Enjoy!

Alex



koppel badge miley badge