Jasmine And Karma

By: Stephen Patrick | 09 Oct 2016 | Category: Jasmine JavaScript Testing

Jasmine And Karma

In this tutorial we will look at how to test with Jasmine and Karma. Karma is a test runner and is a product of AngularJs. Karma can be used to test code on various browser’s and devices. In this tutorial we will use NPM (Node Package Manager) to configure our environment and run our tests.

First we will create the directory jasmine-karma-tests to host our code.

mkdir jasmine-karma-tests

Next, we configure npm.

npm init

Next we will add jasmine, karma, karma-jasmine, and the karma-firefox-launcher as dependencies.

npm install karma jasmine karma-jasmine karma-firefox-launcher

We will also install the karma-cli as a global dependency

npm install -g karma-cli

We will now initialize karma.

karma init

Package.json is updated to use the karma test runner.

"scripts": {
   "test": "karma start --single-run --browsers Firefox karma.conf.js"
},

With the infrastructure configured, the next step is to being writing a test. The aim is to create a Stack data structure. The first file that is created is stackSpec.js this is added to our test folder.

describe("StackSpec", function() {
   var stack;

   beforeEach(function() {
       stack = new collections.Stack();
   });

   it("empty stack length is zero", function() {
       expect(stack.length()).toEqual(0);
   });

});

Above we create our Jasmine spec that will contain all of the expectations used to test / drive our Stack implementation. Above an expectation is written to assert that an empty stack has a length equal to zero.

npm test

As expected on running the above Spec our test fails with Stack is not defined error. In order for the test to pass we begin creating the Stack implementation.

var collections = collections || {};

(function() {
   function Stack() {
       var self = this;
       var dataStore = [];

       self.length = function() {
           return dataStore.length;
       };
   }
   collections.Stack = Stack
}).apply(collections);

The Stack implementation above uses a JavaScript array to hold its elements. We add a public function for length, which simply returns the length of the stack.

npm test

On running the tests again, the tests pass. Next the push method is implemented. The purpose of the push method is to add a new element to the top of the stack.

it("stack push", function() {
   stack.push(1);
   expect(stack.length()).toEqual(1);
   stack.push(2);
   expect(stack.length()).toEqual(2);
   expect(stack.toString()).toEqual("2,1");
});

Next the push method implementation is created

self.push = function(elem) {
   dataStore.push(elem);
};

self.toString = function() {
   if(dataStore.length ==0) {
       return "";
   }

   var result = "";
   for(var i=dataStore.length -1; i>=0; i--) {
       result += dataStore[i].toString();
       result += ",";
   }

   return result.substring(0, result.length-1);
};

The push method uses the array.push method to add an element onto the stack. We also add a toString() method in order for us to return a string representation. The test expectation, pushes two elements onto the stack, asserts the length, and the contents of the stack. Tests are run again to assert everything passes.

npm test

Next the peek method is implemented. The purpose of this method is to show the element at the top of the stack.

self.peek = function() {    
   return dataStore[self.length() - 1];
};

The peek method returns the first element of the underlying array.

it("peek on empty stack returns undefined", function() {
   expect(stack.peek()).not.toBeDefined();
   expect(stack.toString()).toEqual("");
});

Tests are executed to ensure all pass. Next we want to test the peek method on a non empty stack. However, elements must be added to the stack. We have two choices in approaching this. We can use the push method, or modify our implementation to take an initial sequence of values. If we go with the first option, our test is dependent on functionality provided by the push method. Consequently, it is decided to modify the Stack to accept initial values.

function Stack(dataset) {
   ...

   function init(dataset) {
       dataset = dataset || [];

       if(dataset.constructor.name != 'Array') {
           throw "Expected array argument";
       }

       for(var i=0; i<dataset.length; i++) {
           dataStore.push(dataset[i]);
       }
   }

   init(dataset);

Above the Stack implementation is modified to accept an array of values. The values are pushed onto the stack in the given sequence.

it("stack invalid argument", function() {
   expect(function() {
       new collections.Stack({})}).toThrowError(TypeError,
           "Expected array argument");
});


it("stack peek returns top", function() {
   stack = new collections.Stack([1,2,3]);
       expect(stack.peek()).toBe(3);
});

Above two expectations are created. The first is to verify if the Stack is passed an argument that is not an Array it throws a TypeError. The second asserts the peek method – that the top of the stack is equal to 3. Note, elements are pushed onto the stack using the array order.

The final method that will be tested is the pop method. The purpose of this method is to remove and return the top element from the stack.

it("pop on empty stack returns undefined", function() {
   expect(stack.pop()).not.toBeDefined();
   expect(stack.toString()).toEqual("");
});

Above a test is written to assert popping an element from an empty stack returns undefined.

it("pop to return and remove top element", function() {
   stack = new collections.Stack([1,2,3]);
   expect(stack.pop()).toEqual(3);
   expect(stack.toString()).toEqual("2,1");
   expect(stack.length()).toEqual(2);
   expect(stack.pop()).toEqual(2);
   expect(stack.toString()).toEqual("1");
   expect(stack.length()).toEqual(1);
   expect(stack.pop()).toEqual(1);
   expect(stack.toString()).toEqual("");
   expect(stack.length()).toEqual(0);
});

After writing our last expectation to test popping on a non empty stack, our test suite is executed to verify all tests pass.

npm test