Autonomous Machine

Dollar Spec is a tiny JavaScript spec library

Dollar spec is a tiny JavaScript spec framework that gets out of the way and lets you get things done. It supports nested describes and before/after blocks, but that is about it. The standard set of expectations is small right now, but they are easy to add.

Why another JavaScript spec framework?

I wanted a small, pure-JS framework that supported a syntax I didn't have to think too much to use. I wanted to only add as much syntax as was neccessary, and leave the rest to JavaScript. I wanted to run JavaScript tests from the console. I wanted a really extensible way to add new expectations. And I wanted minimal namespace usage. DollarSpec creates a single global entity, $spec.

Also, it seemed like a good way to stretch my brain. I've been doing a lot of JavaScript development recently, and it was fun to apply some of the new techniques I've picked up.

OK Let's See What You've Got

Here's a sample spec for an awesome addition function:

$spec.describe('my awesome addition function', function(spec) {

    var add = function() {
       var total = 0;
       for (var i = 0, l = arguments.length; i < l; i++) {
           total += arguments[i];
       }
       return total;
    };
    
    spec.before(function() {
        // code in this block is run before each spec
        // and yes, describe blocks can be nested
    });
    
    spec.it('adds two numbers', function(expect) {
        expect(add(2,2)).to.equal(4);
    });

    spec.it('adds three numbers', function(expect) {
        expect(add(1,1,2)).to.equal(4);
    });

    // to show a failing spec
    spec.it('adds two numbers', function(expect) {
        expect(add(1,1,2)).to.equal(5);
    });

    // to show a pending spec
    spec.it('casts and adds strings');
});

$spec.run();

If you run this inside Firefox, you will get some messages in the Firebug console:

Filter results

These message can be adjusted to your liking:

// To disable console output (defaults to true)
$spec.opts.console = false;

// Print a line to the console for each test (defaults to false)
$spec.opts.verbose = true;

If you don't want to use the console reporting, the results of all specs are collected and returned by $spec.stats(). This makes it easy to do whatever you wish with the results.

Supported expectations

Keep in mind that any expectation can be negated using not(). For example:

expect(3).to.not().equal(8);

expect(actual).to.be(object)

Compares the expected and actual values using ===.

expect(actual).to.beA(Object)

Verifies that actual is an instance of Object.

expect(actual).to.equal(expected)

Compares actual and expected using ==.

expect(affectorFunction).to.change(affectedFunction)

Compares the value returned from calling affectedFunction before and after calling affectorFunction.

expect(affectorFunction).to.change(affectedFunction).by(amount)

Compares the difference between the value returned from calling affectedFunction before and after calling affectorFunction to amount.

expect(callbackFunction).to.raiseError([verifyFunction])

Verifies that callbackFunction raises an exception when called.

Optionally will call verifyFunction and pass in a raised exception for further inspection. verifyFunction must return true or false.

Custom expectations

DollarSpec's expectations are built from two pieces- verbs and matchers.

Verbs

A verb is a function that can be used in an expectation to define something about the parameters of what is expected. The most basic verbs simply define which matcher to use. Verbs must always return this in order to allow chaining to work. Here is the verb definition for equal:

$spec.verb('equal', function(expected) {
    this.set('matcher', 'equal');
    this.set('expected', expected);
    return this;
});

This verb will cause the expectation to use the equal matcher, and stores the value of expected so it can be examined in the matcher later.

Matchers

Matchers are functions that determine if an expectation passes or fails. They can also define messages to be displayed to the user when a test fails. Here's the beA matcher:

$spec.matcher('beA', function(result) {
  result.failure = "Expected an instance of " + this.klass.toString() + ", but it was " + typeof(this.actual);
  result.negatedFailure = "Expected instance of a class other than " + this.klass.toString() + ", but it was one";

  return this.actual instanceof(this.klass);
});

result.negatedFailure is the message to be displayed when the tests passes, but was used in a negated expectation using not().

You can see how the built-in verbs and matchers are implemented in the Github repository.

Dogfood for Dinner

Dollar Spec has a suite of specs, written using Dollar Spec and powered by diligence, a tiny remote JavaScript test runner library. It requires Node.js, which is awesome. Go check it out if you haven't yet.

To run the specs:

cd spec
node suite.js

Open a browser and go to localhost:5678. You should see something like the following:

Dollar Spec Results

Diligence is still under development, but its working well so far on a few JavaScript projects I'm working on. I'll post more about it as it matures.

More information

There is a bit more info in the README on Github. Feedback is always welcome!

Alternatives

You should probably take a look at ScrewUnit if you're looking for a more complete/mature BDD framework.

Now go write some specs.