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:
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:
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.
- 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
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.
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.
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
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 Songand Player. Each has several functions and properties.
Suites: describe Your Tests
A Suite represents a bunch of tests that are related. Each suite in turn contains a set of Expectations that compare the results of the test - called the actual - with the expected value. A test suite begins with a call to the global Jasmine function
describe with two parameters: a string and a function.Syntax :
describe(<String> , <Function>)
String - is a name or title for a spec suite - (usually what is being tested)
<Function> : The function is a block of code that implements the suite.
Example:
describe("A suite", function() {
---— What needs to be test
});
Specs:
Suites functions contains the calls to the expectation methods/functions called Specs.
Specs are defined by calling the global Jasmine functionit, which, likedescribetakes a string and a function.
describe("A suite", function() { ——————— Suite
it (<String> , <Function> ) ————— Specs
});
<String> : The string is the title of the spec
<Function> : and the function is the spec, or test.
A spec contains one or more expectations that test the state of the code. An expectation in Jasmine is an assertion that is either true or false.
A spec with all true expectations is a passing spec.
A spec with one or more false expectations is a failing spec.
As an <actor> I want to <action> so that <achievment>Expectations are built with the functionexpectwhich takes a value, called the actual. It is chained with a Matcher function, which takes the expected value.
MatchersEach matcher implements a boolean comparison between the actual value and the expected value. It is responsible for reporting to Jasmine if the expectation is true or false. Jasmine will then pass or fail the spec. | it("and has a positive case", function() {
expect(true).toBe(true);
});
|
Any matcher can evaluate to a negative assertion by chaining the call to expect with a not before calling the matcher |
Example:
describe("A suite", function() {
it("contains spec with an expectation", function() {
expect(true).toBe(true);
});
});
Simple Example of a Jasmine Test Case containing both Spec and Suites
Let's say you're making a program that has one function, which says hello to the entire world.
function helloWorld() { return "Hello world!"; }
Now you want to run it by Jasmine to see what she thinks. How do we do that?
- First, put this lovely file into the
/srcdirectory. Let's call itHelloWorld.js.- Now define a suite
describe(“HelloWorld", function() {
});
3.Now define a spec
describe(“HelloWorld", function() {it(“checkingHello”, function(){expect(helloWorld()).toEqual(“Hello World”);});});
//--- CODE --------------------------
function calculateNumbers(a,b) {
return a+b;
}
//--- SPECS -------------------------
describe('JavaScript addition operator', function () {
it('adds two numbers together', function () {
expect(calculateNumbers(2,2)).toEqual(3);
});
});
// 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" );
});
});
var request = require("request");
var base_url = "http://localhost:8080/";
describe("Hello World Server", function() {
describe("GET /", function() {
it("returns status code 200", function() {
request.get(base_url, function(error, response, body) {
});
});
});
});
Next, notice the beforeEach() and afterEach() definitons. These methods are optional but very useful.
beforeEach(function(){ }); afterEach(function(){ });As the name implies, theHere is the same set of specs written a little differently. The variable under test is defined at the top-level scope — thebeforeEachfunction is called once before each spec in thedescribein which it is called, and theafterEachfunction is called once after each spec.describeblock — and initialization code is moved into abeforeEachfunction. TheafterEachfunction resets the variable before continuing.
describe("A spec using beforeEach and afterEach", function() { var foo = 0; beforeEach(function() { foo = 1; }); afterEach(function() { foo = 2; }); it("is just a function, so it can contain any code", function() { expect(foo).toEqual(1); }); it("can have more than one expectation", function() { expect(foo).toEqual(1); expect(true).toEqual(true); }); });
TheHowever, be careful usingbeforeAllfunction is called only once before all the specs indescribeare run, and theafterAllfunction is called after all specs finish. These functions can be used to speed up test suites with expensive setup and teardown.beforeAllandafterAll! Since they are not reset between specs, it is easy to accidentally leak state between your specs so that they erroneously pass or fail.
describe("A spec using beforeAll and afterAll", function() { var foo; beforeAll(function() { foo = 1; }); afterAll(function() { foo = 0; }); it("sets the initial value of foo before specs run", function() { expect(foo).toEqual(1); foo += 1; }); it("does not reset foo between specs", function() { expect(foo).toEqual(2); }); });The
Another way to share variables between athiskeywordbeforeEach,it, andafterEachis through thethiskeyword. Each spec’sbeforeEach/it/afterEachhas thethisas the same empty object that is set back to empty for the next spec’sbeforeEach/it/afterEach.describe("A spec", function() { beforeEach(function() { this.foo = 0; }); it("can use the `this` to share state", function() { expect(this.foo).toEqual(0); this.bar = "test pollution?"; }); it("prevents test pollution by having an empty `this` created for the next spec", function() { expect(this.foo).toEqual(0); expect(this.bar).toBe(undefined); }); });Nesting
Calls todescribeBlocksdescribecan be nested, with specs defined at any level. This allows a suite to be composed as a tree of functions. Before a spec is executed, Jasmine walks down the tree executing eachbeforeEachfunction in order. After the spec is executed, Jasmine walks through theafterEachfunctions similarly.
describe("A spec", function() { var foo; beforeEach(function() { foo = 0; foo += 1; }); afterEach(function() { foo = 0; }); it("is just a function, so it can contain any code", function() { expect(foo).toEqual(1); }); it("can have more than one expectation", function() { expect(foo).toEqual(1); expect(true).toEqual(true); }); describe("nested inside a second describe", function() { var bar; beforeEach(function() { bar = 1; }); it("can reference both scopes as needed", function() { expect(foo).toEqual(bar); }); }); });
Disabling Suites
Suites can be disabled with thexdescribefunction. These suites and any specs inside them are skipped when run and thus their results will not appear in the results.
xdescribe("A spec", function() { var foo; beforeEach(function() { foo = 0; foo += 1; }); it("is just a function, so it can contain any code", function() { expect(foo).toEqual(1); }); });
Pending Specs
Pending specs do not run, but their names will show up in the results aspending.describe("Pending specs", function() {Any spec declared with xitis marked as pending.xit("can be declared 'xit'", function() { expect(true).toBe(false); });Any spec declared without a function body will also be marked pending in results. it("can be declared with 'it' but without a function");And if you call the function pendinganywhere in the spec body, no matter the expectations, the spec will be marked pending.