jShoulda Tutorial

jShoulda, just as the Shoulda testing plugin it's inspired on, makes it easy to write elegant, understandable, and maintainable tests. JavaScript.

A minimal test document

You need the following files to use jShoulda:

Create an HTML document which references both scripts. Include a div element with id testlog, then your testing code. Run it, enjoy.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>

  <title>Minimal jShoulda unit test</title>
  
  <script src="/js/jsunittest.js" type="text/javascript"></script>
  <script src="/js/jshoulda.js" type="text/javascript"></script>
  <link rel="stylesheet" href="/css/unittest.css" type="text/css" media="screen"/>
</head>

<body>
  <h1>Minimal jShoulda unit test</h1>
  
  <div id="testlog">
    Your tests results are shown by default
    in the element whose id is testlog.
  </div>
  
<script type="text/javascript">
  // testing code must appear **after** the #testlog element
  // the script may be included inline or referenced

  // auxiliar function
  function scriptExists(file) {
    var scripts = document.getElementsByTagName('script');
    var script;
    for (var i = 0; i < scripts.length; i += 1) {
      script = scripts[i];
      if (script.src && script.src.indexOf(file) == script.src.length - file.length) {
        return true;
      }
    }
    return false;
  }

  // our testing code
  context('A minimal test', {
      // setup and teardown functions are optional
    },
    should('include a call to jsunittest.js', function() {
      this.assertEqual(true, scriptExists('jsunittest.js'));
    }),
    should('include a call to jshoulda.js', function() {
      this.assertEqual(true, scriptExists('jshoulda.js'));
    })
  )();

</script>
  
</body>
</html>

jShoulda syntax

By default, jShoulda exports two methods to the global space: context and should.

To create a test runner (which will be automatically executed on window.onload), invoke context, then execute the result:

context('The name of the context')();

The first argument to context is the base name for actual tests derived from it. The second is an (optional) object which may include setup and teardown methods:

context('The name of the context', {
  setup: function() {
    // `this` is a Test.Unit.Testcase instance
    this.foo = 1;
  },
  teardown: function() {
    // you don't need to clean up `this`'s properties,
    // the Test.Unit.TestCase is brand new for every actual test
  }
  }
)();

Hint: You can use before/after instead of setup/teardown if it makes you feel better.

Include should calls after the (optional) configuration object to create actual tests:

context('A context', {
  setup: function() {
    this.foo = 1;
  }
  },
  should('run its setup function', function() {
    this.assertEqual(1, this.foo);
  })
)();

// if you don't need setup/teardown methods
context('A context',
  should('run its setup function', function() {
    this.assertEqual(1, this.foo);
  })
)();

Every call to should must pass a name and a callback as arguments. Assertions are run inside the callback, where this points to a Test.Unit instance (the same you can manipulate on setup/teardown).

To create new contexts (on which every setup/teardown function from parent contexts is ran), pass a call to context as an argument:

context('A context', {
  setup: function() {
    this.foo = 1;
  }
  },
  should('run its setup function', function() {
    this.assertEqual(1, this.foo);
  }),
  context('which is a "nested" context', {
    setup: function() {
      this.foo +=1;
    }
    },
    should('run both setup functions', function() {
      this.assertEqual(2, this.foo);
    })
  )
)();

You can nest as much contexts as you like.

Assertions

Assertions come from the JsUnitTest library. Almost every assertion accepts three arguments: the expected value, the actual one and an optional message. An intentionally incomplete list of possible assertions follows:

Customizing the Test Runner

The progress and results of your tests are shown, by default, on a div element whose id equals testlog. If you want to modify the behaviour of the runner, pass a configuration object when invocating the root context call result:

context('A context',
  should('run a test', function() {
    this.assert('Yay!');
  }),
)({ testLog: 'my_test_log_div_id' });

Aliasing

You can easily create alias to context and should methods. In fact, they are alias themselves.

Say you wanted to use some/must instead of (actually, in addition) to context/should. You could just do:

jShoulda
  .setShouldAlias('must')
  .setContextAlias('some');

some('context',
  must('run a test', function() {
    this.assert('Yay!');
  })
)();

Both setBlahAlias create methods which works just as context and should do. But instead of getting a “should” connector, you get a connector named as the method, that is, the full name of the test we've created in the previous example, would be “A context must run a test”.

Pass a second argument to setShouldAlias if you want a different connector (or no connector at all). Pass a second argument to setContextAlias if you want a prefix for your context.

jShoulda
  .setShouldAlias('it', '')
  .setContextAlias('describe');

describe('A context',
  it('runs a test', function() {
    this.assert('Yay!');
  })
)();

or

jShoulda
  .setContextAlias('un', 'Un')
  .setContextAlias('para', 'para')
  .setShouldAlias('deberia', 'debería');

un("programador",
  para("ser feliz",
    deberia("poder comer melocotones de Calanda todo el año", function() {
    })
  )
)();

In this last case, the outputted name for our test will be “Un programador para ser feliz debería poder comer melocotones de Calanda todo el año”.

Of course, you can mix as much aliases as you like.

Extending the test cases (new in 1.2)

Properties in the configuration object (other than setup/teardown/before/after) are copied to each Test.Unit.Testcase inside the context—that is, are accesible through this inside the should callbacks.

context("A 'should' execution", {
  assertLocalYipiyay : function(actual, msg) {
    this.assertEqual('yipiyay', actual, msg);
  }
  },
  should("get access to extra properties defined on the configuration object", function() {
    this.assertLocalYipiyay('yipiyay');
  })
)();

Unifying test runners (new in 1.2)

By default, each root contexts creates a new Test.Unit.Runner instance, so if you're not using different loggers, headers may report wrong results. And, if you are using the rake tasks provided with newjs or rails, only the results of one of the contexts would get logged.

Not a problem? Well... in addition, you can't load tests from multiple files! Isn't that a problem?

jShoulda provides the method unifyRunners. It saves the world.

There are three ways to use the method:

// with no arguments
// every next root context inherits the runner
// from the previous (if any) root context
jShoulda
  .unifyRunners();


// with option arguments
// every next root context uses a new
// runner created from the options
jShoulda
  .unifyRunners({testLog: 'testlog2'});

// if you've created a testRunner through the
// traditional syntax, you can reuse it
var runner = new Test.Unit.Runner({
  testDummy : function() {
    this.assert(true);
  }
});
jShoulda
  .unifyRunners(runner);

Questions?

Feel free to ask on the jShoulda Google Group.

jShoulda home page.