21 April 2015

Jasmine parameterized tests

Parameterized tests are very useful yet still not natively supported by many testing frameworks. Jasmine also doesn't support it by default but it's written in a way that allows us to add such functionality easily.

Let's say we want to test if string's length property works correctly. Of course we want to test it on many different strings. First let's create the 'framework':
function parameters(arrayOfArrays, testCaseFunction) {
     _.each(arrayOfArrays, function(innerArray){
         testCaseFunction.apply(this, innerArray);
     });
}
and... that's it. Now we can run tests:
describe('length property',function(){

   parameters([
      ['abc', 3],
      ['ab',  4],
      ['',    0]
   ], function(string, expected) {
     it('should contain string length', function(){
       expect(string.length).toBe(expected);
     });
   });

});
As expected, we get one error:
length property
  should contain string length
    Expected 2 to be 4.
It's not very informative. It would be good to indicate somehow which case failed. The simplest way is:
parameters([
     ['abc', 3],
     ['ab',  4],
     ['',    0]
  ], function(string, expected) {
    it('should contain string length ' + Array.prototype.slice.call(arguments).toString(), function(){
      expect(string.length).toBe(expected);
    });
  });
Now each description contains its parameters:
length property
  should contain string length ab,4
    Expected 2 to be 4.
But do we have to add this line to every single description? Absolutely not. In a functional language you have huge flexibility in defining your own DSL. How about this one:
forEach([
     ['abc', 3],
     ['ab',  4],
     ['',    0]
])
.it('should contain string length', function(string, expected){
  expect(string.length).toBe(expected);
});
It gives you the following error message:
length property
  should contain string length ["ab",4]
    Expected 2 to be 4.
Is it difficult to write? Not really:
function forEach(arrayOfArrays) { 
   return {
      it : function(description, testCaseFunction) {
              _.each(arrayOfArrays, function(innerArray){
                    it(description + ' ' + JSON.stringify(innerArray), function(){
                        testCaseFunction.apply(this, innerArray);
                    });     
              });
           }
      };
}
You can even do:
  ['abc', 3,
   'ab',  4,
   '',    0].
it('should contain string length', function(string, expected){
   expect(string.length).toBe(expected);
});
With 'infrastructure' as simple as:
Array.prototype.it = function(description, testCaseFunction) {
     _(this)
      .chunk(testCaseFunction.length)
      .each(function(innerArray){
                it(description + ' ' + JSON.stringify(innerArray), function(){
                   testCaseFunction.apply(this, innerArray);
                });     
      })
      .value();
};
And it may be vulnerable to programmer's errors, it may be not the best practice to override arbitrary Array property, it doesn't allow you to include parameters in the description but it certainly demonstrates the flexibility of jasmine and js.

Btw: beforeEach, afterEach etc. work as expected, that is as if there were a bunch of independent it(...) calls.

Tested with jasmine 1.3.1 and lodash 3.7.0.

1 komentarze :

Anonymous said...

Hi,
just used this guide and it's still working nicely with jasmine 2.0. Thanks for saving my time!

Post a Comment