Adobe
Products
Acrobat
Creative Cloud
Creative Suite
Digital Marketing Suite
Digital Publishing Suite
Elements
Photoshop
Touch Apps
Student and Teacher Editions
More products
Solutions
Digital marketing
Digital media
Education
Financial services
Government
Web Experience Management
More solutions
Learning Help Downloads Company
Buy
Home use for personal and home office
Education for students, educators, and staff
Business for small and medium businesses
Licensing programs for businesses, schools, and government
Special offers
Search
 
Info Sign in
Welcome,
My cart
My orders My Adobe
My Adobe
My orders
My information
My preferences
My products and services
Sign out
Why sign in? Sign in to manage your account and access trial downloads, product extensions, community areas, and more.
Adobe
Products Sections Buy   Search  
Solutions Company
Help Learning
Sign in Sign out My orders My Adobe
Preorder Estimated Availability Date. Your credit card will not be charged until the product is shipped. Estimated availability date is subject to change. Preorder Estimated Availability Date. Your credit card will not be charged until the product is ready to download. Estimated availability date is subject to change.
Qty:
Purchase requires verification of academic eligibility
Subtotal
Review and Checkout
Adobe Developer Connection / HTML5, CSS3, and JavaScript /

Unit test JavaScript applications with Jasmine

by Dustin Butler

Dustin Butler

Content

  • What is Jasmine?
  • A little background on TDD and BDD
  • Test suites - describe()
  • Where to go from here

Created

30 April 2012

Page tools

Share on Facebook
Share on Twitter
Share on LinkedIn
Bookmark
Print
HTML5 JavaScript mobile testing

Requirements

Prerequisite knowledge

JavaScript

  • HTML

 

Additional required other products

  • Jasmine

User level

All

For many, the idea of unit testing JavaScript applications seems difficult or unnecessary. Maybe the project does not seem big enough to warrant writing tests. Maybe the complexity of the application seems too difficult to write unit tests for. Problems arise with JavaScript scope, asynchronous XHR requests, DOM manipulation, and all the different browsers and platforms. Sometimes, JavaScript unit testing seems unreliable, brittle, or take more time and effort than it’s worth.

The good news is that dozens of JavaScript test frameworks, libraries, and test runners help solve the problems. These make implementing unit tests for your JavaScript applications quick, reliable, and easy to maintain. The bad news is, there are dozens of JavaScript frameworks, libraries, and test runners to choose from. Selecting a JavaScript test framework can be daunting.

In this article, I discuss some of the basics for writing JavaScript unit tests, some of the concepts behind test-driven development (TDD) and behavior-driven development (BDD). Then, you can apply that information to writing and running JavaScript unit tests with Jasmine.

What is Jasmine?

Jasmine is a behavior-driven development (BDD) JavaScript testing framework Pivotal Labs maintains it and makes it available on GitHub under the MIT license. Jasmine allows tests to be written independent of the DOM or other JavaScript frameworks. You run Jasmine in a browser, or headless without a browser by integrating with other frameworks such as Rhino, Envy, or the Jasmine-headless-webkit library.

Jasmine API includes features such as:

  • A more natural BDD syntax for organizing the test logic than JUnit style assertion test frameworks
  • Asynchronous testing
  • Mocks
  • Spies
  • Easy to create custom matchers
  • Ability to share or isolate behaviors between tests within a spec encapsulating parts of your spec.
  • Continuous integration support

Many available libraries add additional functionality to the already robust Jasmine API. These include jasmine-ui, jasmine-features, and jasmine-jQuery for testing through the DOM, or Jasmine-species for changing the grammatical nature of the tests.

Jasmine is different from test frameworks like Selenium, which test the client application as a simulated user through the browser. It is similar to frameworks like QUnit, which programmatically test the JavaScript code directly. If you’ve written tests with other assertion frameworks, you will pick up Jasmine quickly.

Its syntax and test suite implementation are similar to Ruby’s RSpec framework, which takes a BDD approach to writing the tests. This syntax is different from the jUnit, QUnit, and FlexUnit style assertion frameworks that tend to verify the code from the inside out. Jasmine syntax is more narrative. It describes the behavior the code is supposed to support. It is written similar to the way an agile user story is written. And testing happens from the outside in. More on this later.

What kinds of tests should I use Jasmine for?

There are a variety of test types, for example smoke tests, integration tests, user acceptance tests, regression tests, and unit tests. Ideally, an application has a full testing story that includes all of these test types. Jasmine is not best suited to support them all. Jasmine is a unit testing framework, ideally suited to run unit tests and regression tests. Technically, you can use it to partially test integration between two systems. But, I do not recommend relying on Jasmine for 100% of your integration tests, because Jasmine only tests the Javascript code on one system at a time. Similar to integration tests, do not rely on Jasmine for 100% of your user acceptance testing. Tests are only as thorough as they are written. Acceptance testing is done, ideally, by business or end users.

What is a unit test?

At its core, a unit test is a program (known as a test case, or test specification) that isolates and tests a specific and small functional unit of source code. Never test too many pieces of code at one time. And, unlike with an integration test, do not use unit tests to test more than one system at a time.

Why write unit tests?

You probably already have an understanding of the value of unit testing your code. But if you are still skeptical, here are a few reasons that I hope convince you to write them.

  • Unit tests demonstrate the code functions as designed.
  • Unit tests can be run every time you compile your code or do a build. This consistency is especially useful when a developer checks in a change that inadvertently affects code in another area of the application.
  • Running a suite of unit tests is faster than manual testing.
  • Unlike manually testing your application by clicking through the fields in your browser, you are guaranteed that the tests run the same way every time. That is not to say that unit testing makes system testing in the browser unnecessary, but it does ensure a certain level of confidence in the code.
  • Writing unit tests helps developers write better code. Whether you chose to write the unit tests before the code is written or after, the tests often persuade the developer to write code that is better structured, easier to maintain, and “composable” (in other words, modular and usually stateless).
  • Unit tests provide documentation for the source code. These tests describe the interface to your code.
  • Over time, you acquire a comprehensive suite of tests against the code base.
  • Uncovering bugs before going to QA is cheaper.

A little background on TDD and BDD

I won’t go deep into Test Driven Development (TDD) theory since it’s a bigger subject than I can cover in this article. Many articles on the web go into detail on the subject. What’s important here are the basics of the process and of how they relate to BDD. BDD uses TDD as a starting point, and then takes it a few steps further.

TDD emphasizes writing unit tests (ideally) at the beginning of the application development process to help the developer design the code. Essentially designing your object and method interfaces through the test, and refactoring the code and tests and the code matures. The tests are often written as a white box test- inside-out which tests the specific implementation of a piece of code.

In a nut-shell, TDD suggests the user take the following steps when developing:

  1. Before any coding starts, write a test to design the code against a specification.
  2. Run the test. The test fails because there isn’t any code in place to support the code.
  3. Write some code.
  4. Test.
  5. Refactor.
  6. Test again and fix bugs and implementation.

BDD is an agile software development technique that also emphasizes writing unit tests. A difference is that instead of focusing on the inside-out test approach, it approaches the test from a business value perspective which tests outside-in. In doing so, test authors focus on why a piece of code is necessary, and what its goal is.

Test Syntax

A sample QUnit test case looks like the following code:

// your applications custom code function addValues( a, b ) {     return a + b; }; // the QUnit test code test("test addValues(a, b) function", function() {     equal(addValues( 1, 2), 3, "1 + 2 = 3" );     equal(addValues( 1.75, 2), 3.75, "1.75 + 2 = 3.75" );     notEqual(addValues( 1, 2), "3", "1 + 2 != '3' as a String"); });

A sample Jasmine test case is written as follows:

// your applications custom code function addValues( a, b ) {     return a + b; }; // the Jasmine test code describe("addValues(a, b) function", function() {     it("should equal 3", function(){         expect( addValues(1, 2) ).toBe( 3 );     });     it("should equal 3.75", function(){         expect( addValues(1.75, 2) ).toBe( 3.75 );      });     it("should NOT equal '3' as a String", function(){         expect( addValues(1, 2) ).not.toBe( "3" );     }); });

Notice both Qunit and Jasmine are testing the same things. But the Jasmine approach is easier to read, even for someone who does not know JavaScript. And if you have written Agile user stories, the structure of the test narrative also looks familiar, similar to the standard Agile user story format:

As an <actor> I want to <action> so that <achievment>

Setting up Jasmine

You have many options for running your Jasmine tests. Tests can be run manually, in browsers, headless outside a browser, via a continuous integration server (CI), and so on. The unit tests are written the same way regardless of how they are run. Some examples of how to run them are:

  • SpecRunner.html: Available in the standalone version. You can run tests from a local browser. No server required.
  • From your IDE: For an example of how to run tests automatically from Eclipse, see this post from Misko Heverly.
  • JS-Test-Driver: Able to run tests in multiple browsers. You can run JS-Test-Driver from your IDE, command line, and so on.
  • Java with Maven: Runs on every check in, and can then test generated JavaScript.
  • Run headless in Rhino, Envy, or Jasmine-headless-webkit: for testing without a browser. Because the headless test does not attach to a browser instance, it can run faster.
  • Many others: check the Jasmine documentation.

The easiest way to get started with Jasmine is to manually run the tests from SpecRunner.html, which comes in the standalone version of Jasmine. You can download the standalone version of Jasmine from the Pivotal GitHub. Be sure to download 1.2.0 rc2 or newer. The 1.1.0 release did not include all the files to run in the SpecRunner.html file.

Un-zip the file and place it anywhere on your local file system. Open the Jasmine folder and take a look at the directory structure. Notice three directories and an html file:

  • lib
  • src
  • spec
  • SpecRunner.html

The most important directory is lib, which contains the Jasmine test framework code. The src and spec directories contain example tests for your reference. SpecRunner.html is the file used to invoke the unit tests.

None of the directories have to use this naming convention. Your tests do not need to be in the same root directory as the Jasmine source files. However, unless you change the Jasmine JavaScript files, those do need to remain in the same directory. In a real world project, you keep the SpecRunner.html, Jasmine test files, and lib files in a directory that is never deployed to your production server. And in the SpecRunner.html file, you source in the JavaScript files you are testing.

Running the unit tests

Launching the SpecRunner.html file in your local browser runs the tests. Jasmine provides a nice view of the test results. If you are not happy with the look or layout of the result display, there are even libraries to change it, such as jasmine-species.

Figure 1. Jasmine provides a view of the test results and has libraries to customize it.
Figure 1. Jasmine provides a view of the test results and has libraries to customize it.

You can change the format and information displayed in the test runner. The browser name, version, and platform are not displayed with the test information. To change the format of the test info, or add more, edit the jasmine-html.js file under libs. Also, use the available libraries in jasmine-reporters to output the report to XML. Some test runners, such as JS-Test-Driver, which runs Jasmine tests, use their own test result display and save the results to a file.

SpecRunner configuration

Open the SpecRunner.html file to see how the tests are defined.

Figure 2. Note how tests are defined in the SpecRunner.html file.
Figure 2. Note how tests are defined in the SpecRunner.html file.

The file contains four blocks of script tags. The first section includes the Jasmine test runner files. In the next two sections, you reference your JavaScript code to be tested as well as the tests. The fourth runs the tests. Notice that the comments for the included script blocks are backwards. Swap them for clarity. Retain this script organization for your tests and code.

At this point you may be thinking: What should I do if I need to test JavaScript in my HTML files? Well, remember the refactor part in the TDD discussion? The specrunner is not set up to include html files. So, refactor your code to separate the JavaScript in to its own JavaScript file, and then source that file into the test and your html. It’s a best practice anyway. The tests are already helping you write better code! If your JavaScript requires manipulating DOM elements, use libraries such as jasmine-fixtures and jasmine-jquery. These libraries allow you to write unit tests with mock html in them.

Let’s look at the example code and tests that came with the Jasmine standalone download. Open the Player.js and Song.js files located in the src directory to review the functions in them. They each contain the basic Object examples Song and Player. Each has several functions and properties.

Test suites - describe()

Next, open the test suite file under spec/PlayerSpec.js. This file contains your test suite. You can have as many test suites as you like, organized in as many JavaScript files as you chose. A simple test suite has the following basic structure:

describe(){     beforeEach()     afterEach()     it(){         expect()     } }

At the top of PlayerSpec.js, notice the line of code that says:

describe("Player", function() { });

In Jasmine, describe() function are used to define a test suite. Suites are used to organize test specifications and can also contain other suites. Suites are a way to organize the specs and assign scope to them.  In your PlayerSpec example, notice three suites are defined, each with specs and sometimes other suites. You must define a suite at the root, before you define specs. Code in each suite is only run once.

Defining a suite takes two arguments. The first is a label for the name of your suite that is printed in the test results. The second is a function closure that contains the suites specs and other logic you wish scoped under the spec.

To comment out a suite, change describe() to xdescribe().

beforeEach() & afterEach()

Next, notice the beforeEach() and afterEach() definitons. These methods are optional but very useful.

beforeEach(function(){ }); afterEach(function(){ });

beforeEach(), and its counterpart, afterEach() are utility methods used for set up and tear down of your tests. In the PlayerSpec.js example, create a new Player() and Song() before each test is run. afterEach() is run after each test is run. In PlayerSpec.js, the beforeEach() at the top level is called for every test run. Due to scoping, the beforeEach() nested in the second describe() block is called only for tests in that block. Jasmine scoping in the suite is handy for managing the code you need run.

Test specifications – it()

Test specifications (called specs in Jasmine) are where your tests are defined. A test spec is called with it(), and takes two arguments: A String description of the purpose of the tests, and a function closure that contains the tests.

it("should demonstrate a specific behavior", function(){ });

A spec can contain as many tests as you like. It can contain the logic to set up the tests. And it can clean up after it runs the tests.  Logic you find yourself writing for more than one test specification is a candidate to go into a beforeEach() and afterEach() block.

To comment out a spec, change it() to xit().

Tests and test matchers – Expect()

Inside the it() block, you define the tests. You define as many tests as you need. Tests are defined with the expect(actual) function, followed by a matcher such as toEqual(value) or toBeGreaterThan(value).

it("should demonstrate a specific behavior", function(){    expect( yourCustomAddFunction(1,1) ).toEqual( 2 ); });

expect(actual) is similar to JSUnit and QUnit assert() function. The value you pass into expect() is the value you are testing.

Matchers are functions that test that your “actual” value meets the expected criteria, and returns a Boolean true or false depending on the result.

When you need to do a negative test, use expect( foo ).not property.

it("should demonstrate a specific behavior", function(){    expect( yourCustomAddFunction(1,1) ).not.toEqual( null ); });

Jasmine ships with many matchers. You can also write your own if the ones Jasmine provides do not meet your needs. For the whole list, refer to the Jasmine matchers documentation at Pivotal’s GitHub.

Custom matchers

If Jasmine does not have a matcher that works for your test, you can write your own.  There are three parts to adding new matchers to your testing toolbox:

  • Create the matcher function.
  • Add it to an object as a function. The property name you give the function is the matcher name you will use.
  • Add it to your Jasmine test suite.

You can define the matcher inline, when you add the matcher to your spec. Or you can define your matcher outside Jasmine and reference it in when you register the matcher. Your matcher can take as many arguments as you need. Jasmine assigns the argument you passed into the expect function to this.actual, which you reference in your matcher function.

You register the matchers within the beforeEach() block of a suite with this.addMatchers by passing it an object that has the matchers defined in it.

var myGlobalLessThanMatcher = function ( expected ){ return this.actual < expected; }; var myTestAmount = 5; describe("Player", function() { beforeEach(function(){ this.addMatchers({ toBeMoreThan: function( expected ){ return this.actual > expected; }, toBeLessThan : myGlobalLessThanMatcher }); }); it(“should be greater or less than myTestAmount”), function() { expect( 10 ).toBeMoreThan( myTestAmount ); expect( myTestAmount ).toBeLessThan( 10 ); }); }

Spies

Jasmine spies allow you to indirectly test if functions have been called, and to inspect the arguments they were called with. This is useful when you have a series of functions chained together, and you want to ensure that logic somewhere down the chain has been called with an expected value.

Using the Player Object in Player.js from the test code you downloaded with Jasmine, modify it to add spies. Add a new function to the Player Object to toggle the song play and pause.

Player.prototype.togglePlay = function(song) {     if ( this.isPlaying ) {         this.pause();     }     else {         this.play(song);     } }

And then create a new test spec:

describe("when a song is playing, we can toggle between play and pause", function() {     beforeEach(function() {         player.play(song);     });     it("should pause the song", function() {         spyOn( player, "pause" ); // define the spy         player.togglePlay( song );         expect( player.pause ).toHaveBeenCalled();         expect( player.pause ).toHaveBeenCalledWith( );         });         it("should play the song", function() {             spyOn( player, "play" ); // define the spy             player.pause(); // just called here to set up our test to play the song next.             player.togglePlay( song );             expect( player.play ).toHaveBeenCalled();             expect( player.play ).toHaveBeenCalledWith( song );          }); });

In our specs, you are spying on Player.play( song ) and Player.pause(), but notice that you never directly call either of those functions. They are called by player.togglePlay(). You can test if the functions have been called with the spy specific matcher toHaveBeenCalled(). And you can test the arguments used to call it with toHaveBeenCalledWith().  In the case of player.pause(), you don’t pass any arguments to that function, so toHaveBeenCalledWith() is defined with empty params.

Also use spies for faking a function call, returning mock data from a call, and injecting properties and functions into an Object. These uses are good for mocking asynchronous tests or to overwrite a function for your test purposes.

describe("some mock examples", function(){     // When player.pause() is called, alert a message instead of calling pause()     it("should call a different function than pause()", function() {         spyOn( player, "pause" ).andCallFake( alert(“The song was paused”) );     });     // Force a return value of player.play() to be a String "The song is playing"     it("should return a String that says 'the song is playing'", function() {         spyOn( player, "play" ).andReturn( "The song is playing" );         expect( player.play() ).toEqual("The song is playing");     }); });

If you are interested, take a further look at the Jasmine documentation for other spy methods and properties.

Asynchronous tests

Defining an asynchronous test is fairly easy. Jasmine provides ways to simulate asynchronous testing, such as AJAX, events, and other delayed function calls by either setting a timer, or you can wait for a function to return true before moving forward. Combined with spies, you can modify the response to mock in data and change result values.

An asynchronous spec is composed with one or more runs() functions, and your choice of waits( time ) or waitsFor( function, msg, maxTimeOUt ) functions to delay code execution in your spec. Note that waitsFor( function ) calls its function repeatedly until it returns true, so beware if you are incrementing values or progressively changing  model data. Also note that variables inside each run() block are scoped to that block.

The two examples below demonstrate ways to delay an async test: with a timer using waits( time ), and based on the return value of a function, such as an event or callback from your AJAX call, with waitsFor().

//Simulates an async function by waiting until it has been called 50 times. var globalCounter = 0; var pingCounter = 0 function returnAfterWait( ){     pingCounter++;     if ( pingCounter == 50 ){         globalCounter++;         return true;     }                         }                             describe("This is an async test", function(){     //this test waits 500 ms before testing the results     it("should test async with timer", function(){         var counter = 0;         runs( function(){             setTimeout( function(){ counter++; }, 500 );         })         waits( 505 );         runs( function(){             expect(counter).toEqual( 1 );         })     });     /*         This test waits to continue until         returnAfterWait() returns true     */     it("should test async with a return", function(){         var counter = 0;         waitsFor( function() {             return returnAfterWait(); }, "this is the async message", 5000 );         runs( function(){ expect(globalCounter).toEqual( 1 ); })     }); });

Where to go from here

Whether you are developing in an agile environment with user stories, or developing your own small website, Jasmine helps you develop better code. And if you adopt TDD, it can reduce your development time.

Out of the box, Jasmine has a lot of ways to run the tests, a large library of useful matchers, spies, and asynchronous testing tools. Writing the tests is quick and easy. If you need help with more functionality or features, use third-party libraries. Add JQuery integration. Change report formatting and syntax, and how to run the tests. If you don’t find what you are looking for on the Jasmine website, search the web for other blogs and GitHub for other Jasmine libraries.

Find more information about some of the topics covered in this article at the following sites:

  • Pivotal Labs on Github
  • Testing Your Javascript
  • Behavior Driven Development
  • Test Driven Development
  • Agile Software Development

Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License+Adobe Commercial Rights

This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License. Permissions beyond the scope of this license, pertaining to the examples of code included within this work are available at Adobe.

More Like This

  • Real-time data exchange in HTML5 with WebSockets
  • Introducing the HTML5 storage APIs
  • Introducing theexpressiveweb.com beta
  • Adobe, standards, and HTML5
  • Developing HTML5 games with Impact JavaScript game engine and Dreamweaver CS5.5
  • Using the Geolocation API
  • CSS3 basics
  • Backbone.js Wine Cellar tutorial – Part 2: CRUD
  • JavaScript object creation: Learning to live without "new"
  • Object types in JavaScript

Products

  • Acrobat
  • Creative Cloud
  • Creative Suite
  • Digital Marketing Suite
  • Digital Publishing Suite
  • Elements
  • Mobile Apps
  • Photoshop
  • Touch Apps
  • Student and Teacher Editions

Solutions

  • Digital marketing
  • Digital media
  • Web Experience Management

Industries

  • Education
  • Financial services
  • Government

Help

  • Product help centers
  • Orders and returns
  • Downloading and installing
  • My Adobe

Learning

  • Adobe Developer Connection
  • Adobe TV
  • Training and certification
  • Forums
  • Design Center

Ways to buy

  • For personal and home office
  • For students, educators, and staff
  • For small and medium businesses
  • For businesses, schools, and government
  • Special offers

Downloads

  • Adobe Reader
  • Adobe Flash Player
  • Adobe AIR
  • Adobe Shockwave Player

Company

  • News room
  • Partner programs
  • Corporate social responsibility
  • Career opportunities
  • Investor Relations
  • Events
  • Legal
  • Security
  • Contact Adobe
Choose your region United States (Change)
Choose your region Close

North America

Europe, Middle East and Africa

Asia Pacific

  • Canada - English
  • Canada - Français
  • Latinoamérica
  • México
  • United States

South America

  • Brasil
  • Africa - English
  • Österreich - Deutsch
  • Belgium - English
  • Belgique - Français
  • België - Nederlands
  • България
  • Hrvatska
  • Česká republika
  • Danmark
  • Eastern Europe - English
  • Eesti
  • Suomi
  • France
  • Deutschland
  • Magyarország
  • Ireland
  • Israel - English
  • ישראל - עברית
  • Italia
  • Latvija
  • Lietuva
  • Luxembourg - Deutsch
  • Luxembourg - English
  • Luxembourg - Français
  • الشرق الأوسط وشمال أفريقيا - اللغة العربية
  • Middle East and North Africa - English
  • Moyen-Orient et Afrique du Nord - Français
  • Nederland
  • Norge
  • Polska
  • Portugal
  • România
  • Россия
  • Srbija
  • Slovensko
  • Slovenija
  • España
  • Sverige
  • Schweiz - Deutsch
  • Suisse - Français
  • Svizzera - Italiano
  • Türkiye
  • Україна
  • United Kingdom
  • Australia
  • 中国
  • 中國香港特別行政區
  • Hong Kong S.A.R. of China
  • India - English
  • 日本
  • 한국
  • New Zealand
  • 台灣

Southeast Asia

  • Includes Indonesia, Malaysia, Philippines, Singapore, Thailand, and Vietnam - English

Copyright © 2012 Adobe Systems Incorporated. All rights reserved.

Terms of Use | Privacy Policy and Cookies (Updated)

Ad Choices

Reviewed by TRUSTe: site privacy statement