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");
});
});
<h2>And the Output</h2>
<h3>In Firebug</h3>
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
<h2>Some caveats</h2>
<ul>
<li>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</li>
<li>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.</li>
</ul>
<a href="http://www.yehudakatz.com/wp-content/uploads/jspec.zip">Happy hunting (Download here)</a>
<p><b>Update</b></p>
<p>There is now a git repo for the project at git.caboo.se/jspec.git</p>