Yehuda Katz is a member of the Ruby on Rails core team, and lead developer of the Merb project. He is a member of the jQuery Core Team, and a core contributor to DataMapper. He contributes to many open source projects, like Rubinius and Johnson, and works on some he created himself, like Thor.
@drawohara I replied. I want it to work in Rubies with truly simultaneous threads without the potential for garbage data.
JSpec: BDD for JS
November 9th, 2007
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.
Update
There is now a git repo for the project at git.caboo.se/jspec.git

Phil, Posted November 9, 2007, 8:02 pm
Let’s get a repo going. I’m gonna mix it up with some mozrepl magick. http://hyperstruct.net/projects/mozrepl/
Jörn Zaefferer, Posted November 9, 2007, 9:22 pm
That example looks interesting.
I wonder why you use should(”include”, …) instead of should_include(…) or should(”==”, …) instead of should_equal(…). You’ve started adding to Object.prototype anyway, so why stop halfway through?
On a side note: What is BDD?
wycats, Posted November 9, 2007, 9:31 pm
@Jörn: This way people can add to the matchers by just modifying the jspec.matchers object, instead of needing to define a new method on the Object prototype.
Also, while I have extended the Object prototype, I really don’t like it, and want to keep the number of extended objects finite and determinable (that way someone can still test jQuery.extend as long as they understand that should and should_not will be on the new Object erroneously).
Jörn Zaefferer, Posted November 10, 2007, 8:49 am
How about returning an object from should() that provides the methods you want to use? Something like 7.should().equal(…) or document.should().exist().
That way you can keep the number of methods on Object.prototype low and still stick with methods instead of parameters.
David Parker, Posted January 28, 2008, 3:48 pm
How does this work with JQuery? Note: I’m not a JavaScript master yet.
Tj, Posted March 15, 2009, 2:11 am
I have a library named JSpec as well, far more robust
and will top pretty much every javascript bdd framework out there once it hits 1.0
http://github.com/visionmedia/jspec/tree/master
Tj, Posted April 6, 2009, 2:14 pm
Just released 1.0, check it out at http://visionmedia.github.com/jspec/