Stubbing with Moksi

Manfred Stienstra

In the summer of 2008 I wrote a small mocking and stubbing library for JavaScript called Moksi. I used it a little bit in a project and then forgot about it. When we picked up the project again a few weeks ago I was happy to rediscover Moksi. Because there isn’t much written about testing in JavaScript, I figured it might be a good idea to share some tips and tricks.

We do most of our testing with unittest.js which was developed as part of the testsuite for script.aculo.us. If you’re not familiar with it: Thomas Fuchs did a nice presentation at RailsConf 2006 about it. You might want to read up if you don’t know what I’m taling about.

So now you have assertions galore, but what do you do when you don’t want to recreate an entire scenario in the DOM just to test one or two things? You stub them.

Suppose you want to test the following function code:

var Autosave = Class.create({
  initialize: function(form) {
    this.form = $(form);
  },
  
  installObserver: function() {
    this.form.observe('change', function() {
      this.saveAsDraft();
    });
  },
  
  saveAsDraft: function() {
    form.request({ parameters: { draft: true }});
  }
});

The problem might be that you want to make sure the form is saved as draft, but you don’t actually want to do the AJAX request.

new Test.Unit.Runner({
  setup: function() {
    // Create a form in the DOM and initialize an Autosave.
    $('sample').innerHTML = '<form action="" id="testform"><input type="checkbox" value="money" name="get" /></form>';
    
    this.autosave = new Autosave('testform');
  },
  
  teardown: function() {
    Moksi.revert();
  }
  
  testFormIsSavedAsDraftWhenChanged: function() { with(this) {
    // Stub Form.request on the form and record its requests
    Moksi.stub(this.autosave.form, 'request', function(options) {
      this.autosave.form.requests = this.autosave.form.requests || [];
      this.autosave.form.requests.push(options);
    }.bind(this));
    
    this.autosave.saveAsDraft();
    
    self.assertEqual(1, this.autosave.form.requests.length);
    
    var requestOptions = this.autosave.form.requests.last();
    self.assertNotNull(requestOptions.parameters);
    self.assertEqual(true, requestOptions.parameters.draft);
  }}
});

Notice the Moksi.revert() call? That makes sure all your stubs get reverted after running each test, which is really helpful if you only want to stub a function in one test and use the normally defined function in all the others.