JavaScript Unit Testing Part 2: JSSpec

In this post we will examine the 2nd of 4 JavaScript Unit Testing frameworks... JSSpec.

Previous parts of this series...

In subsequent posts we will cover QUnit, and YUI Test.

<p>I like this Unit Testing framework a lot more than JsUnit for the following reasons...</p><ul><li>As you can see from the screenshot below, it has a nice interactive Test Runner</li>   <li>The testing API has an easy to read <a href="http://en.wikipedia.org/wiki/Fluent_interface">Fluent Interface</a> (a.k.a. It reads like English)</li>   <li>The project appears to still be active... the latest version was available on <a href="http://code.google.com/p/jsspec/downloads/list">Sep 23, 2008</a></li>   <li>The test files don't need to be hosted in a web server in order to run (unlike JsUnit).</li></ul><div><a href="http://2.bp.blogspot.com/_L6DiZQsfJzs/Seh2yK6z7KI/AAAAAAAAGyY/PJKcwcfoVMc/s1600-h/PigLatin.PNG" imageanchor="1"><img border="0" src="http://2.bp.blogspot.com/_L6DiZQsfJzs/Seh2yK6z7KI/AAAAAAAAGyY/PJKcwcfoVMc/s400/PigLatin.PNG"></a></div> <p>Ok, ok, enough talk. Lets get down to using this framework...</p> <p>I added functionality to the nasty Pig Latin JavaScript to not only translate words, but also sentences.*</p> <p>*Note: Yet again, this code is not optimal and it will be refactored in a future post.</p> <pre><br>function EnglishToPigLatin() {<br>    this.CONSONANTS = 'bcdfghjklmnpqrstvwxyz';<br>    this.VOWELS = 'aeiou';<br>    this.Translate = function(english, splitType) {<br>        var translated = '';    <br><br>        console.log('English: ', english);<br>        var words = english.split(/\s+/);<br>        console.log('Split Words: ', words);<br>        for (var i = 0; i < words.length; ++i) {<br>            console.log('Word ', i, ': ', words[i]);<br>            translated += this.TranslateWord(words[i]);<br>            if (i+1 < words.length) translated += ' ';<br>        }<br>        console.log('Translated: ', translated);<br>        console.log('----------');<br><br>        return translated;<br>    }<br>    this.TranslateWord = function(english) {<br>       /*const*/ var SYLLABLE = 'ay';<br><br>       var pigLatin = '';<br><br>       if (english != null && english.length > 0 && <br>          (this.VOWELS.indexOf(english[0].toLowerCase()) > -1 || this.CONSONANTS.indexOf(english[0].toLowerCase()) > -1 )) {<br>          if (this.VOWELS.indexOf(english[0].toLowerCase()) > -1) {<br>             pigLatin = english + SYLLABLE;<br>          } else {      <br>             var preConsonants = '';<br>             for (var i = 0; i < english.length; ++i) {<br>                if (this.CONSONANTS.indexOf(english[i].toLowerCase()) > -1) {<br>                   preConsonants += english[i];<br>                   if (preConsonants.toLowerCase() == 'q' && i+1 < english.length && english[i+1].toLowerCase() == 'u') {<br>                      preConsonants += 'u';<br>                      i += 2;<br>                      break;<br>                   }<br>                } else {<br>                   break;<br>                }<br>             }<br>             pigLatin = english.substring(i) + preConsonants + SYLLABLE;<br>          }<br>       }<br><br>       return pigLatin;    <br>    }<br>} </pre> <p>Like I said before, JSSpec provides a nicer Fluent Interface that makes the tests read more like English. The follow are several sets of Unit Tests to exercise the existing functionality, plus the new feature I added since the last post.</p> <pre><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><br><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ko"><br><head><br><meta http-equiv="Content-Type" content="text/html;charset=UTF-8" /><br><title>JSSpec results</title><br><link rel="stylesheet" type="text/css" href="JSSpec.css" /><br><script type="text/javascript" src="diff_match_patch.js"></script><br><script type="text/javascript" src="JSSpec.js"></script><br><script type="text/javascript" src="PigLatinBad.js"></script><br><script type="text/javascript">// <![CDATA[<br>describe('Invalid Arguments', {<br>    before_each : function() { englishToPigLatin = new EnglishToPigLatin(); },<br>    'Passing Null Should Return Blank': function() {<br>        value_of(englishToPigLatin.TranslateWord(null)).should_be('');<br>    },<br>    'Passing Blank Should Return Blank': function() {<br>        value_of(englishToPigLatin.TranslateWord('')).should_be('');<br>    },    <br>    'Passing 1234567890 Should Return Blank': function() {<br>        value_of(englishToPigLatin.TranslateWord('1234567890')).should_be('');<br>    },<br>    'Passing ~!@#$%^&*()_+ Should Return Blank': function() {<br>        value_of(englishToPigLatin.TranslateWord('~!@#$%^&*()_+')).should_be('');<br>    }    <br>})<br><br>describe('Consonant Words', {<br>    before_each : function() { englishToPigLatin = new EnglishToPigLatin(); },<br>    'Passing Beast Should Return Eastbay': function() {<br>        value_of(englishToPigLatin.TranslateWord('beast')).should_be('eastbay');<br>    },<br>    'Passing Dough Should Return Oughday': function() {<br>        value_of(englishToPigLatin.TranslateWord('dough')).should_be('oughday');<br>    },<br>    'Passing happy Should Return appyhay': function() {<br>        value_of(englishToPigLatin.TranslateWord('happy')).should_be('appyhay');<br>    },<br>    'Passing question Should Return estionquay': function() {<br>        value_of(englishToPigLatin.TranslateWord('question')).should_be('estionquay');<br>    },<br>    'Passing star Should Return arstay': function() {<br>        value_of(englishToPigLatin.TranslateWord('star')).should_be('arstay');<br>    },<br>    'Passing three Should Return eethray': function() {<br>        value_of(englishToPigLatin.TranslateWord('three')).should_be('eethray');<br>    }<br>})<br><br>describe('Vowel Words', {<br>    before_each : function() { englishToPigLatin = new EnglishToPigLatin(); },<br>    'apple Should Return appleay': function() {<br>        value_of(englishToPigLatin.TranslateWord('apple')).should_be('appleay');<br>    },<br>    'elijah Should Return elijahay': function() {<br>        value_of(englishToPigLatin.TranslateWord('elijah')).should_be('elijahay');<br>    },<br>    'igloo Should Return iglooay': function() {<br>        value_of(englishToPigLatin.TranslateWord('igloo')).should_be('iglooay');<br>    },<br>    'octopus Should Return octopusay': function() {<br>        value_of(englishToPigLatin.TranslateWord('octopus')).should_be('octopusay');<br>    },    <br>    'umbrella Should Return umbrellaay': function() {<br>        value_of(englishToPigLatin.TranslateWord('umbrella')).should_be('umbrellaay');<br>    }    <br>})<br><br>describe('Sentences', {<br>    before_each : function() { englishToPigLatin = new EnglishToPigLatin(); },<br>    "Passing 'hello' Should Return 'elloh'": function() {<br>        value_of(englishToPigLatin.Translate('hello')).should_be('ellohay');<br>    },<br>    "Passing 'hello world' Should Return 'elloh orldw'": function() {<br>        value_of(englishToPigLatin.Translate('hello world')).should_be('ellohay orldway');<br>    },<br>    "Passing 'hello world!' Should Return 'ellow orld!w'": function() {<br>        value_of(englishToPigLatin.Translate('hello world!')).should_be('ellohay orld!way');<br>    },<br>    "Passing 'Hello World' Should Return 'elloH orldW'": function() {<br>        value_of(englishToPigLatin.Translate('Hello World')).should_be('elloHay orldWay');<br>    },    <br>    "Passing 'Hello World!' Should Return 'elloH orld!W'": function() {<br>        value_of(englishToPigLatin.Translate('Hello World!')).should_be('elloHay orld!Way');<br>    }<br>})<br>// ]]></script><br></head><br><body><div style="display:none;"><p>A</p><p>B</p></div></body><br></html></pre> <p>There are other asserts that can be made that I didn't make use of in the above same. Here are some examples... (taken from the <a href="http://jania.pe.kr/aw/moin.cgi/JSSpec/Manual">JSSpec Manual</a>)</p><ul><li>value_of('Hello'.toLowerCase()).should_be('hello');</li>   <li>value_of([1,2,3]).should_be([1,2,3]);</li>   <li>value_of(new Date(1979,03,27)).should_be(new Date(1979,03,27));</li>   <li>value_of([]).should_be_empty();</li>   <li>value_of(1 == 1).should_be_true();</li>   <li>value_of(1 != 1).should_be_false();</li>   <li>value_of("Hello").should_have(5, "characters");</li>   <li>value_of([1,2,3]).should_have(3, "items")</li>   <li>value_of({name:'Alan Kang', email:'jania902@gmail.com', accounts:['A', 'B']}).should_have(2, "accounts");</li>   <li>value_of([1,2,3]).should_have_exactly(3, "items");</li>   <li>value_of([1,2,3]).should_have_at_least(2, "items");</li>   <li>value_of([1,2,3]).should_have_at_most(4, "items");</li>   <li>value_of([1,2,3]).should_include(2);</li>   <li>value_of([1,2,3]).should_not_includ</li>   <li>value_of({a: 1, b: 2}).should_include("a");</li>   <li>value_of({a: 1, b: 2}).should_not_include("c");</li></ul><p>As you can see, this is a very powerful easy to read and execute testing framework. Of those frameworks I have tested thus far, I recommend it above the others (at this point I've only covered JsUnit).</p>