Archive for the 'JavaScript' Category

JSpec: BDD for JS

A couple of weeks ago I wrote a nice little piece of JS that implemented BDD-style testing in JS. It was originally 70 or so lines of code, and supported code like:

jspec.describe("Some stuff", function() {
  it("should rock", function() {
    7.should("==", 7);
  });
});

It was (and still is) a very simple piece of code, that only dumps the output to the Firebug console. Because should() was a simple method, I was able to delegate out to a pluggable jspec.matchers object, which is where I specified the “match” definition, and allowed the customization of a “failure message.” The original jspec had just two matchers (”==” and “include”), and allowed the creation of new ones by extending the jspec.matchers object (all JS, no magic).

When I tried to actually use it to test real-world code, though, some issues came up. First, functions were being dumped in full into the output, which was sucky. Secondly, the default describe syntax was sucking a bit for certain cases. And finally, I wanted you to be able to plug in any logger you wanted (for say, pretty HTML output). I added those new features, and am now releasing this quick and dirty code for people to look at.

Some examples

(which are included in the downloadable file)

jspec.describe("JSpec", function() {
  it("should support ==", function() {
    (1).should("==", 1);
    var arr = [];
    arr.should(”==”, arr);
    var obj = new Object;
    obj.should(”==”, obj);
    document.should(”==”, document);
  });

  it(”should support include”, function() {
    [1,2,3,4,5].should(”include”, 3);
    [1,2,3,4,5].should_not(”include”, 3);
    document.getElementsByTagName(”div”).should(”include”, document.getElementById(”hello”))
  });

  it(”should support exists”, function() {
    document.should(”exist”);
  });

  jspec.matchers["have_tag_name"] = {
    describe: function(target, not) {
      return jspec.compress_lines(this) + ” should ” + (not ? “not ” : “”) + “have ” + target + ” as its tag name.”
    },
    matches: function(target) {
      return (this.tagName && this.tagName == target) ? true : false;
    },
    failure_message: function(target, not) {
      return “Expected ” + this.toString() + (not ? ” not ” : ” “) + “to have ” + target + ” as its tag name,” +
        ” but was ” + this.tagName;
    }
  };

  it(”should support custom matchers”, function() {
    document.getElementById(”wrapper”).should(”have_tag_name”, “DIV”);
    document.getElementById(”wrapper”).should_not(”have_tag_name”, “SPAN”);
    document.getElementById(”wrapper”).should(”have_tag_name”, “SPAN”);
  });
});

And the Output

In Firebug

JSpec
  - should support ==
    - 1 should equal 1 (PASS)
    - [] should equal [] (PASS)
    - [object Object] should equal [object Object] (PASS)
    - [object HTMLDocument] should equal [object HTMLDocument] (PASS)
  - should support include
    - 1,2,3,4,5 should include 3 (PASS)
    - 1,2,3,4,5 should not include 3 (FAIL)
      Expected [[1,2,3,4,5]] not to include 3
    - [object HTMLCollection] should include [object HTMLDivElement] (PASS)
  - should support exists
    - [object HTMLDocument] should exist. (PASS)
  - should support custom matchers
    - [object HTMLDivElement] should have DIV as its tag name. (PASS)
    - [object HTMLDivElement] should not have SPAN as its tag name. (PASS)
    - [object HTMLDivElement] should have SPAN as its tag name. (FAIL)
      Expected [object HTMLDivElement] to have SPAN as its tag name, but was DIV

Some caveats

  • Despite some work put in, I can’t figure out how to get jspec to recover gracefully from errors and continue on. This will work in a future release
  • I temporarily override the Object prototype (evil, I know) for the duration of the test. You will still be able to load in libraries that expect Object to work correctly, but you will not be able to test functions (such as prototype’s Object.extend) without accounting for the fact that Object.prototype has been extended.

Happy hunting (Download here)

Update

There is now a git repo for the project at git.caboo.se/jspec.git

Give JavaScript 2 a Look

Last week, I attended a talk on JavaScript 2 by John Resig. John wrote jQuery, and is now the JS evangelist for Mozilla, which is pushing JS2 along with Adobe and Opera.

Before the talk, I was fairly ambivalent about the new language. In the abstract, it has the ability to inflict pain on us humble JS developers if the upgrade path is too problematic, and there are a whole load of language features that aren’t obviously interesting (again in the abstract).

John’s presentation was, in effect, a Keynote version of the recently released Ecmascript 4 Language Overview Whitepaper, which was released by Adobe, Mozilla, and Opera, three members of the ECMA working group.

The presentation was incredibly code-heavy, and very interesting. As it currently stands, the EcmaScript 4 draft attempts to provide a class structure to EcmaScript 3 (ES3), without breaking backwards-compatibility with ES3, similar to the way C++ added classes to C while preserving back-compatibility.

Since one of the major goals of ES4 is to remain compatible with ES3, existing code will continue to run, and be progressively enhanced by using new features in ES4. The new classical system will exist side-by-side with the old prototypal system, and the new strongly typed system will exist side-by-side with the old weakly typed system.

Here are some examples:

Strict typing in ES3

function(x, y) {
  if(x.constructor != Number) throw TypeError();
  if(y.constructor != String) throw TypeError();
  // code
}

Strict typing in ES4

function(x:Number, y:String) {
  // code
}

The former pattern is extremely common in ES3, so the new feature will simply provide a language-supported way to do what you’re already able to do with functions. That said, the type system has some interesting features:

  • Union types: var x:(int, string)
  • Nullability: var x:int! only accepts an int, which cannot be null
  • *: var x:* is equivalent to the ES3 var x

Remaining variables in functions in ES3

function(x, y) {
  var rest = [].slice.call(arguments, 2);
  // code
}

Remaining variables in functions in ES4

function(x, y, ...rest) {
  // code
}

Again, ES4 simply makes a pattern that is extremely common in existing use simpler and more error-proof. And again, nothing stops your ES3 code from continuing to work.

The classical system

ES4 provides a new classical system, including real Classes, Inheritance, and Interfaces. The classes can be defined as dynamic (similar to ES3 classes, in that properties can be added after the fact), or non-dynamic, which throws an error if a programmer attempts to add a new property. A quick example:

class Foo {
	var bar;
	var baz = 7;
}
var x = new Foo;
x.bat = 12 #=> throws Error

But with a dynamic class:

dynamic class Foo {
	var bar;
	var baz = 7;
}
var x = new Foo;
x.bat = 12 #=> sets x

So you can use dynamic classes to give more structure to existing “classes”, and non-dynamic classes to lock down created classes from further change.

Inheritence

Inheritance in ES3 is done via the prototype chain. You might do something like this:

var Foo = function() { }
var Bar = function() { }
Bar.prototype = new Foo();

In ES4, inheritence is more explicit:

class Foo {}
class Bar extends Foo {}

Non-overridable functions

You can prevent classes from being inherited from, or methods in classes from being overridden using the final keyword, as follows:

final class Foo {}
class Bar extends Foo {} #=> throws Error
class Foo {
	final function bar() { }
}

class Bar extends Foo {
	function bar() { } #=> throws Error
}

Summary

I touched on only a few of the new features in ES4. For the most part, these features could be implemented using ES3 (and there are a number of examples above), but the ES3 counterparts are often verbose and repetitive. I encourage people who are interested in the language to read through the white paper. It’s surprisingly readable for a language spec, and describes a fair bit of motivation.

For people who currently write a fair bit of JavaScript code, I hope you’ll see the new features in ES4 as encapsulations of current, common patterns, and solutions to problems that arise when attempting to develop large systems using JavaScript.

Finally, the current draft is certainly not perfect, but it is a solid core for the next revision of the language. If there’s something you don’t like, feel free to join the ES4 discussion list and raise your concerns. A healthy discussion with more developers will help move the process forward, and away from the back-room politics that is currently miring it.