JavaScript Unit Testing Part 2: JSSpec

17. April 2009

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.

I like this Unit Testing framework a lot more than JsUnit for the following reasons...

  • As you can see from the screenshot below, it has a nice interactive Test Runner
  • The testing API has an easy to read Fluent Interface (a.k.a. It reads like English)
  • The project appears to still be active... the latest version was available on Sep 23, 2008
  • The test files don't need to be hosted in a web server in order to run (unlike JsUnit).

Ok, ok, enough talk. Lets get down to using this framework...

I added functionality to the nasty Pig Latin JavaScript to not only translate words, but also sentences.*

*Note: Yet again, this code is not optimal and it will be refactored in a future post.

      function EnglishToPigLatin() {
          this.CONSONANTS = 'bcdfghjklmnpqrstvwxyz';
          this.VOWELS = 'aeiou';
          this.Translate = function(english, splitType) {
              var translated = '';    
              
              console.log('English: ', english);
              var words = english.split(/\s+/);
              console.log('Split Words: ', words);
              for (var i = 0; i < words.length; ++i) {
                  console.log('Word ', i, ': ', words[i]);
                  translated += this.TranslateWord(words[i]);
                  if (i+1 < words.length) translated += ' ';
              }
              console.log('Translated: ', translated);
              console.log('----------');
              
              return translated;
          }
          this.TranslateWord = function(english) {
             /*const*/ var SYLLABLE = 'ay';
      
             var pigLatin = '';
                
             if (english != null && english.length > 0 && 
                (this.VOWELS.indexOf(english[0].toLowerCase()) > -1 || this.CONSONANTS.indexOf(english[0].toLowerCase()) > -1 )) {
                if (this.VOWELS.indexOf(english[0].toLowerCase()) > -1) {
                   pigLatin = english + SYLLABLE;
                } else {      
                   var preConsonants = '';
                   for (var i = 0; i < english.length; ++i) {
                      if (this.CONSONANTS.indexOf(english[i].toLowerCase()) > -1) {
                         preConsonants += english[i];
                         if (preConsonants.toLowerCase() == 'q' && i+1 < english.length && english[i+1].toLowerCase() == 'u') {
                            preConsonants += 'u';
                            i += 2;
                            break;
                         }
                      } else {
                         break;
                      }
                   }
                   pigLatin = english.substring(i) + preConsonants + SYLLABLE;
                }
             }
             
             return pigLatin;    
          }
      } 
      

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.

         <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
         <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ko">
         <head>
         <meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
         <title>JSSpec results</title>
         <link rel="stylesheet" type="text/css" href="JSSpec.css" />
         <script type="text/javascript" src="diff_match_patch.js"></script>
         <script type="text/javascript" src="JSSpec.js"></script>
         <script type="text/javascript" src="PigLatinBad.js"></script>
         <script type="text/javascript">// <![CDATA[
         describe('Invalid Arguments', {
             before_each : function() { englishToPigLatin = new EnglishToPigLatin(); },
             'Passing Null Should Return Blank': function() {
                 value_of(englishToPigLatin.TranslateWord(null)).should_be('');
             },
             'Passing Blank Should Return Blank': function() {
                 value_of(englishToPigLatin.TranslateWord('')).should_be('');
             },    
             'Passing 1234567890 Should Return Blank': function() {
                 value_of(englishToPigLatin.TranslateWord('1234567890')).should_be('');
             },
             'Passing ~!@#$%^&*()_+ Should Return Blank': function() {
                 value_of(englishToPigLatin.TranslateWord('~!@#$%^&*()_+')).should_be('');
             }    
         })
         
         describe('Consonant Words', {
             before_each : function() { englishToPigLatin = new EnglishToPigLatin(); },
             'Passing Beast Should Return Eastbay': function() {
                 value_of(englishToPigLatin.TranslateWord('beast')).should_be('eastbay');
             },
             'Passing Dough Should Return Oughday': function() {
                 value_of(englishToPigLatin.TranslateWord('dough')).should_be('oughday');
             },
             'Passing happy Should Return appyhay': function() {
                 value_of(englishToPigLatin.TranslateWord('happy')).should_be('appyhay');
             },
             'Passing question Should Return estionquay': function() {
                 value_of(englishToPigLatin.TranslateWord('question')).should_be('estionquay');
             },
             'Passing star Should Return arstay': function() {
                 value_of(englishToPigLatin.TranslateWord('star')).should_be('arstay');
             },
             'Passing three Should Return eethray': function() {
                 value_of(englishToPigLatin.TranslateWord('three')).should_be('eethray');
             }
         })
         
         describe('Vowel Words', {
             before_each : function() { englishToPigLatin = new EnglishToPigLatin(); },
             'apple Should Return appleay': function() {
                 value_of(englishToPigLatin.TranslateWord('apple')).should_be('appleay');
             },
             'elijah Should Return elijahay': function() {
                 value_of(englishToPigLatin.TranslateWord('elijah')).should_be('elijahay');
             },
             'igloo Should Return iglooay': function() {
                 value_of(englishToPigLatin.TranslateWord('igloo')).should_be('iglooay');
             },
             'octopus Should Return octopusay': function() {
                 value_of(englishToPigLatin.TranslateWord('octopus')).should_be('octopusay');
             },    
             'umbrella Should Return umbrellaay': function() {
                 value_of(englishToPigLatin.TranslateWord('umbrella')).should_be('umbrellaay');
             }    
         })
         
         describe('Sentences', {
             before_each : function() { englishToPigLatin = new EnglishToPigLatin(); },
             "Passing 'hello' Should Return 'elloh'": function() {
                 value_of(englishToPigLatin.Translate('hello')).should_be('ellohay');
             },
             "Passing 'hello world' Should Return 'elloh orldw'": function() {
                 value_of(englishToPigLatin.Translate('hello world')).should_be('ellohay orldway');
             },
             "Passing 'hello world!' Should Return 'ellow orld!w'": function() {
                 value_of(englishToPigLatin.Translate('hello world!')).should_be('ellohay orld!way');
             },
             "Passing 'Hello World' Should Return 'elloH orldW'": function() {
                 value_of(englishToPigLatin.Translate('Hello World')).should_be('elloHay orldWay');
             },    
             "Passing 'Hello World!' Should Return 'elloH orld!W'": function() {
                 value_of(englishToPigLatin.Translate('Hello World!')).should_be('elloHay orld!Way');
             }
         })
         // ]]></script>
         </head>
         <body><div style="display:none;"><p>A</p><p>B</p></div></body>
         </html>
      

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 JSSpec Manual)

  • value_of('Hello'.toLowerCase()).should_be('hello');
  • value_of([1,2,3]).should_be([1,2,3]);
  • value_of(new Date(1979,03,27)).should_be(new Date(1979,03,27));
  • value_of([]).should_be_empty();
  • value_of(1 == 1).should_be_true();
  • value_of(1 != 1).should_be_false();
  • value_of("Hello").should_have(5, "characters");
  • value_of([1,2,3]).should_have(3, "items")
  • value_of({name:'Alan Kang', email:'jania902@gmail.com', accounts:['A', 'B']}).should_have(2, "accounts");
  • value_of([1,2,3]).should_have_exactly(3, "items");
  • value_of([1,2,3]).should_have_at_least(2, "items");
  • value_of([1,2,3]).should_have_at_most(4, "items");
  • value_of([1,2,3]).should_include(2);
  • value_of([1,2,3]).should_not_includ
  • value_of({a: 1, b: 2}).should_include("a");
  • value_of({a: 1, b: 2}).should_not_include("c");

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).

Unit Testing


Comments

9/21/2009 5:05:35 PM #
Unit Testing with jQuery using FireUnit

Unit Testing with jQuery using FireUnit

Add comment




  Country flag

biuquote
  • Comment
  • Preview
Loading