Introduction to Jasmine
Jasmine is an open source automated testing framework based on behavior driven development that help test your JavaScript code. It can run and execute on any platform or wherever JavaScript runs. Jasmine doesn't depend on any browsers, DOM or any other JavaScript framework. But combining Jasmine with frameworks like requireJS and JSCover while testing will help make the tests more effective.
BDD
Behavior-driven development is a development technique which makes use of the combination of Tests driven development and domain driven design. It explains the scenarios in sentences or user stories rather than functional tests. Thus, it helps create a development process which developers, business or any stake holders could easily understand. It increases collaboration between developers, testers and analysts by combining the functional, technical activities and sharing project resources. How far Jasmine belongs to BDD classification is a debatable question.
Setting Up Jasmine
Setting up Jasmine is not a hard job. You may download the latest standalone version from here. For setting up a standalone version, all you have to do is to write the test and give proper references to Jasmine library files, tests and the actual script inside the spec runner. Spec runner is an HTML file in which we have to refer to Jasmine library files, your JavaScript files and Jasmine test file in the order in which they are mentioned. Opening the spec runner will run the rest and you can see the results immediately.
Writing Your First Test
Let's say I have a file helloworld.js and I have to write tests for an addNumbers
function which accepts 2 numbers and returns its sum. To start writing the tests, we have to create a JavaScript file and create a test suite first. I will name that file as helloworldTest.js. Open the file in your favorite editor and start creating tests. First, we have to create test suite before the test case. We can have multiple test suites in a file. Below is the code to create a test suite. A test case should reside inside a test suite.
describe('test suite name',function(){
}
Here, describe
is the function used to create a test suite. Inside the test suite, the first parameter is the test suite name in quotes. This should specify the purpose of the test suite. After that, inside the callback of that function, we can start writing the test.
describe('Addition specs',function(){
it('should add 2 valid numbers and returns valid result', function(){
expect(addNumbers(1,1)).toEqual(2);
});
});
Now to run this, include the jasmine library files and source file and then the test file to the spec runner and open the file. This will run the test and show you the result in a user friendly page.
<title>Spec Runner</title>
<!-- load jasmine -->
<link rel="stylesheet" type="text/css" href="tools/Jasmine/jasmine.css">
<script type="text/javascript" src="tools/Jasmine/jasmine.js"></script>
<script type="text/javascript" src="tools/Jasmine/jasmine-html.js"></script>
<!-- include source files here... -->
<script type="text/javascript" src="src/HelloWorld.js"></script>
<!-- include spec files here... -->
<script type="text/javascript" src="specs/HelloWorldTest.js"></script>
Matchers
In the last example, we compared the result of our function addNumbers(1,1)
to 2
using toEqual()
. Here, this toEqual()
is called a matcher. We have a few matchers provided by Jasmine itself, here are some of them. All Matchers will return a boolean value back and thus the jasmine will either let the test pass or make it fail.
.toMatch()
.toBeUndefined()
.toBeTruthy()
.toBeFalsy()
.toContain()
.toBeLessThan()
.toBeGreaterThan()
.toThrow()
.toBeCloseTo()
.toBe()
We can negate these matchers by prefixing with .not
. For example:
.not.toBeTruthy()
If you think that these built in matchers are not fulfilling your requirement, you may create your own matchers as well.
Custom Matchers
Using this feature of jasmine, we create custom matchers and use them instead of the built in ones. The below code demonstrates how we can create custom matchers for checking if any number is greater than 5
.
this.addMatchers({
IsGreaterthanFive: function () {
return this.actual>5 }
});
Now, you may write your test with your own matcher that you just built.
beforeEach and afterEach
If you want to run some code before executing each test, it is painful to write it in every test, in order to avoid that situation and reuse the code, you can include that code inside the beforeEach
method. So whenever you are executing any test under the suite, the code inside will also execute before each test. So this is handy when we want to set values to default or so, based on your requirement.
var count=0;
beforeEach(function()
{
count=count+1;
console.log("Started executing test no : " + count);
});
If beforeEach
executes before every test, as you may have already guessed, afterEach
executes just after each test completes its execution. So this can be used to clear any objects that are changed due to the previous test or so, again based on what you want to do.
afterEach(function()
{
console.log(Completed executing test no : " + count);
});
Spy
A functionality of application may be dependent on some other functionality. In order to test a functionality, we may not require to call all the dependent methods which are not in the scope of test. Some of the dependencies like constants are cheap but some functionality like an Ajax call may take a long time to respond due to many reasons like delay in network. If the Ajax call is a dependent of the actual method and you want to keep their implementation away from the test, then you don't have to call them, instead you can make a double of that call using spy.
SpyOn
If we want to create spy of an existing object, we can make use of spyOn
method.
spyOn(Obj, "getSumofNumbers")
The .and
object of spyOn
provides a few options to decide how the spy should behave.
callThrough()
: We can combine callThrough()
to the spy object will track all the call and will delegate it to the actual implementation. So in-effect, the actual call will get invoked.
spyOn(Obj, "getSumofNumbers").and.callThrough();
returnValue()
: Using returnValue()
, we can easily return whatever value we want as the ouput of the function. Here in the below example, all calls to the getRandomNumbers
method will return 231
as output.
spyOn(Obj, "getRandomNumber").and.returnValue(231);
CallFake()
: callFake()
simply delegates the function call to the callback function inside the callFake
function.
it('should add 2 valid numbers and returns fake method result',function(){
spyOn(window,'getSumofNumbers').and.callFake(function(){return 5;});
expect(getSumofNumbers(1,2)).toEqual(5);
});
Stub()
: Stub
method is used to create a fake of the actual method. While using the callThrough
, it actually runs through the implementation of the actual method using the spied object, but stub doesn’t have that implementation. It is useful while testing the function call is really called or not, but can’t test the behavior of the method.
it('should stub returns an undefined obect',function(){
spyOn(window,'getSumofNumbers').and.callFake(function(){return 5;});
window.getSumofNumbers.and.stub();
expect(getSumofNumbers(1,2)).toBe(undefined);
});
it('should check whether function call is invoked',function(){
spyOn(window,'getSumofNumbers').and.callFake(function(){return 5;});
window.getSumofNumbers.and.stub();
var x = window.getSumofNumbers(1,2);
expect(window.getSumofNumbers).toHaveBeenCalled();
});
CreateSpy
CreateSpy
is basically used to create a spy on a method which is not in the scope of the test context or there is no method to spy on. These methods will not have an implementation, so by using createSpy
, we can make the function provide us the result that we want.
If you want your spied method to return a value, you may use andReturn(Value)
to tell Jasmine what you want in return.
user.getDept = jasmine.createSpy('getDept()').andReturn('IT');
If you want to define the method body, we can use the andCallFake()
method to write the function inside.
user.getDept = jasmine.createSpy('getDept()'). andCallFake( function(){
var Dept= 'I'+'T';
Return Dept;
});
CreateSpyObj
CreateSpyObj
is used to create an Object
with spies of each names that we supply as the property of the object
.
car = jasmine.createSpyObj('car', ['accelerate','break','cluch',]);
So created spy will have all the functionalities of spy function
car.break.andReturn('break now');
car.accelerate.andCallFake(function() {return 'accelerated'});
Ignoring Tests Suites
As we already know, we are defining a test suite using a describe
function. If for any particular reason we don't want to run the test and the result to show up, we can mark it ignored by prefixing the describe
with x
.
xdescribe('ignored test suite',function(){});
Ignoring Tests
Similar to test suites, we can ignore an individual test case by prefixing the it
with x
.
xit('ignored test',function(){ })
Conclusion
Unit tests are an inevitable part of a solution, Jasmine helps us cover the gap of lacking an effective client side test framework. Even though it is effective, the flexibility of the languages like Spying has to be used wisely while writing the test cases, otherwise they may effect the quality of test.
Hope this brief article helped you understand how to write awesome test cases for JavaScript code.
Helpful References
Jasmine source is available in GitHub.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.