CSC/ECE 517 Fall 2014/ch1b 26 sa

From Expertiza_Wiki
Jump to navigation Jump to search

Jasmine

Jasmine is an open source Behavior Driven Development (BDD) based testing framework for JavaScript. It is maintained by Pivotal Labs and is available on GitHub under the MIT license. Jasmine allows tests to be written independent of the DOM or other JavaScript frameworks. Jasmine can be run in a browser, or headless without a browser by integrating with other frameworks such as Rhino, Envy, or the Jasmine-headless-webkit library.

Jasmine is often used in the Rails framework. Jasmine is available as a Rubygem and it automates common workflows by leveraging known rake tasks and generators. Jasmine is highly influenced by other unit testing frameworks, such as ScrewUnit, JSSpec, JSpec, and RSpec; and has syntax very similar to RSpec.

Background

The Jasmine framework was developed to address certain specific concerns in existing testing frameworks <ref name="Background">https://github.com/pivotal/jasmine/wiki/Background</ref>

  • Many frameworks only work from within a browser.
  • Many frameworks don't support testing asynchronous code like event callbacks.
  • Some frameworks have syntax that is hard for JS developers or IDEs to understand.

Design Principles

The Jasmine developer community believes that a good JavaScripttesting framework must follow these principles. <ref name="Background"/>

  • It shouldn't be tied to any browser, framework, platform, or host language.
  • It should have idiomatic and unsurprising syntax.
  • It should work anywhere JavaScriptcan run, including browsers, servers, phones, etc.
  • It shouldn't intrude in an application's territory (e.g. by cluttering the global namespace).
  • It should play well with IDEs (e.g. test code should pass static analysis).

Goals

The Jasmine framework is written with the objective of meeting these goals.<ref name="Background"/>

  • It should encourage good testing practices.
  • It should integrate easily with continuous build systems.
  • It should be simple to get started with.

Usage

Jasmine aims to be easy to read. A simple hello world test looks like the code below, where describe() describes a suite of tests and it() is an individual test specification. The name "it()" follows the idea of behavior-driven development and serves as the first word in the test name, which should be a complete sentence. Usage follows syntax similar to that of RSpec.<ref name="Usage">http://en.wikipedia.org/wiki/Jasmine_(JavaScript_framework)</ref>

describe('Hello world', function() { 
   it('says hello', function() {
   expect(helloWorld()).toEqual("Hello world!");
 });
});


Features

Suites

Test suites are generally used to group similar test cases together. In Jasmine a test suite begins with a call to the global function describe with two parameters: a string and a function. The string is a name or title for a spec suite – usually what is under test. The function is a block of code that implements the suite.<ref name="Features">http://jasmine.github.io/1.3/introduction.html</ref>

describe("A suite", function() {
 it("contains spec with an expectation", function() {
   expect(true).toBe(true);
 });
});

Specs

Specs are defined by calling the global Jasmine function it, which, like describe takes a string and a function. The string is a title for this spec and the function is the spec, or test. A spec contains one or more expectations that test the state of the code under test.<ref name="Features"/>

An expectation in Jasmine is an assertion that can be either true or false. A spec with all true expectations is a passing spec. A spec with one or more expectations that evaluate to false is a failing spec.

describe("A suite is just a function", function() {
 var a;
 it("and so is a spec", function() {
   a = true;
  expect(a).toBe(true);
 });
});

Disabling Specs and Suits

Suites and specs can be disabled with the xdescribe and xit functions, respectively. These suites and specs are skipped when run and thus their results will not appear in the results.<ref name="Features"/>

xdescribe("A spec", function() {
 var foo;
 beforeEach(function() {
   foo = 0;
   foo += 1;
 });
 xit("is just a function, so it can contain any code", function() {
   expect(foo).toEqual(1);
 });
});

Expectations

Expectations are built with the function expect which takes a value, called the actual. It is chained with a Matcher function, which takes the expected value.<ref name="Features"/>

Matchers

Each 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. Any matcher can evaluate to a negative assertion by chaining the call to expect with a not before calling the matcher.<ref name="Features"/>

describe("The 'toBe' matcher compares with ===", function() {
 it("and has a positive case ", function() {
   expect(true).toBe(true);
 });
it("and can have a negative case", function() {
   expect(false).not.toBe(true);
 });
});

Included Matchers

Jasmine has a rich set of matchers included. Some of them are listed below.<ref name="Included Matchers">http://www.htmlgoodies.com/beyond/javascript/testing-javascript-using-the-jasmine-framework.html</ref>

  • toBe: represents the exact equality (===) operator.
  • toEqual: represents the regular equality (==) operator.
  • toMatch: calls the RegExp match() method behind the scenes to compare string data.
  • toBeDefined: opposite of the JS "undefined" constant.
  • toBeUndefined: tests the actual against "undefined".
  • toBeNull: tests the actual against a null value - useful for certain functions that may return null, like those of regular expressions (same as toBe(null))
  • toBeTruthy: simulates JavaScript boolean casting.
  • toBeFalsy: like toBeTruthy, but tests against anything that evaluates to false, such as empty strings, zero, undefined, etc…
  • toContain: performs a search on an array for the actual value.
  • toBeLessThan/toBeGreaterThan: for numerical comparisons.
  • toBeCloseTo: for floating point comparisons.
  • toThrow: for catching expected exceptions.


There is also the ability to write custom matcherswhen a project’s domain calls for specific assertions that are not included.

Grouping Related Specs with describe

The describe function is for grouping related specs. The string parameter is for naming the collection of specs, and will be concatenated with specs to make a spec’s full name. This aids in finding specs in a large suite. If they are named well, specs read as full sentences in traditional BDD style.<ref name="Features"/>

describe("A spec", function() {
 it("is just a function, so it can contain any code", function() {
   var foo = 0;
   foo += 1;
   expect(foo).toEqual(1);
 });
 it("can have more than one expectation", function() {
   var foo = 0;
   foo += 1;
   expect(foo).toEqual(1);
   expect(true).toEqual(true);
 });
});

Setup and Teardown

To help a test suite DRY up any duplicated setup and teardown code, Jasmine provides the global beforeEach and afterEach functions. As the name implies the beforeEach function is called once before each spec in the describe is run and the afterEach function is called once after each spec.<ref name="Features"/>

describe("A spec (with setup and tear-down)", 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);
 });
});

Spies

In Jasmine, test doubles are called spies. A spy can stub any function and tracks calls to it and all arguments. A spy only exists in the describe or it block it is defined in, and is removed after each spec. There are special matchers for interacting with spies. The toHaveBeenCalled matcher returns true if the spy is called. The toHaveBeenCalledWith matcher returns true if the argument list matches any of the recorded calls to the spy.<ref name="New Features">http://jasmine.github.io/edge/introduction.html</ref>

describe("A spy", function() {
 var foo, bar = null;
 beforeEach(function() {
    foo = {
     setBar: function(value) {
       bar = value;
     }
   };
   spyOn(foo, 'setBar');
   foo.setBar(123);
   foo.setBar(456, 'another param');
 });
 it("tracks that the spy was called", function() {
   expect(foo.setBar).toHaveBeenCalled();
 });
 it("tracks all the arguments of its calls", function() {
   expect(foo.setBar).toHaveBeenCalledWith(123);
   expect(foo.setBar).toHaveBeenCalledWith(456, 'another param');
 });
 it("stops all execution on a function", function() {
   expect(bar).toBeNull();
 });
});

Asynchronous Calls

Jasmine also supports testing of asynchronous calls using the functions runs and waitsFor:<ref name="Asynchronous Calls">https://www.inexfinance.com/en/blog/2013/1/25/jasmine_for_clientside_testing</ref>

  • runs – takes the asynchronous function for execution;
  • waitsFor – takes three parameters: the first one – is a latch function that should return true if the asynch call made in runs was executed, the second one – is a failure message, and the last one - is the waiting time in milliseconds.
describe "Asynchronous", ->
 a = 0
 async = ->
   setTimeout((-> a = 5), 1000)
 it "asynch executes code", ->
   runs(-> async())
   waitsFor((-> a == 5), "the value should be changed", 3000)

Jasmine Clock

The Jasmine Clock is available for testing time dependent code. It is installed with a call to jasmine.clock().install in a spec or suite that needs to manipulate time. The clock needs to be uninstalled after the original functions are restored.<ref name="New Features"/>

Mocking the JavaScript Timeout Functions

Functions like setTimeout or setInterval can be made synchronous executing the registered functions only once the clock is ticked forward in time. To execute registered functions, move time forward via the jasmine.clock().tick function, which takes a number of milliseconds.<ref name="New Features"/>

Mocking the Date

The Jasmine Clock can also be used to mock the current date.If not provided with a base time to mockDate it will use the current date.<ref name="New Features"/>

The following example demonstrates usage of jasmine Clock.

describe("Manually ticking the Jasmine Clock", function() {
 var timerCallback;
 beforeEach(function() {
   timerCallback = jasmine.createSpy("timerCallback");
   jasmine.clock().install();
 });
 afterEach(function() {
   jasmine.clock().uninstall();
 });
 it("causes a timeout to be called synchronously", function() {
   setTimeout(function() {
     timerCallback();
   }, 100);
   expect(timerCallback).not.toHaveBeenCalled();
   jasmine.clock().tick(101);
   expect(timerCallback).toHaveBeenCalled();
 });
 it("causes an interval to be called synchronously", function() {
   setInterval(function() {
     timerCallback();
   }, 100);
   expect(timerCallback).not.toHaveBeenCalled();
   jasmine.clock().tick(101);
   expect(timerCallback.calls.count()).toEqual(1);
   jasmine.clock().tick(50);
   expect(timerCallback.calls.count()).toEqual(1);
   jasmine.clock().tick(50);
   expect(timerCallback.calls.count()).toEqual(2);
 });
describe("Mocking the Date object", function(){
   it("mocks the Date object and sets it to a given time", function() {
     var baseTime = new Date(2013, 9, 23);
jasmine.clock().mockDate(baseTime);
jasmine.clock().tick(50);
     expect(new Date().getTime()).toEqual(baseTime.getTime() + 50);
   });
 });
});

Matching Anything with jasmine.any

jasmine.any takes a constructor or “class” name as an expected value. It returns true if the constructor matches the constructor of the actual value.<ref name="New Features"/>

describe("jasmine.any", function() {
 it("matches any value", function() {
   expect({}).toEqual(jasmine.any(Object));
   expect(12).toEqual(jasmine.any(Number));
 });
 describe("when used with a spy", function() {
   it("is useful for comparing arguments", function() {
     var foo = jasmine.createSpy('foo');
     foo(12, function() {
       return true;
     });
    expect(foo).toHaveBeenCalledWith(jasmine.any(Number), jasmine.any(Function));
   });
 });
});

Behavior Driven Design

Behavior Driven Development (abbreviated as BDD) is a software development process that was developed in response to issues encountered with Test Driven Development (TDD). BDD is based on the techniques and principles of TDD, while combining these with ideas from domain-driven design and object-oriented analysis and design. BDD focuses on the behavioral specification of software units.

While TDD is quite non-specific about what should be tested and at what level of detail, BDD specifies that tests of any unit of software should be specified in terms of the desired behavior of the unit. BDD also specifies how the behavior should be specified. BDD uses a semi-formal format for behavioral specification which is borrowed from user story specifications from the field of object-oriented analysis and design.

Generally TDD suggests writing unit tests at the beginning of the application development process to help the developer design the code. This helps the developer design the object and method interfaces through test, and refactor the code and tests as the code matures. These tests are often written as a white box test-inside-out that test the specific implementation of a piece of code. BDD also emphasizes writing unit tests, but instead of focusing on the inside-out test approach, it approaches the test from a business value perspective which tests outside-in. BDD emphasizes that test authors focus on why a piece of code is necessary, and what its goal is. <ref name="Adobe">http://www.adobe.com/devnet/html5/articles/unit-test-javascript-applications-with-jasmine.html</ref>

This approach is adopted in the way all tests are written in Jasmine. In Jasmine each test describes the behavior of the unit under test, through an "it should do the following" kind of clause.


Comparison between Jasmine, Qunit and Mocha

The following gives a difference between three popular JavaScript test frameworks - Jasmine, Qunit and Mocha.<ref name="Comparison">https://github.com/thegrtman/javascript-test-framework-comparison</ref>

Frameworks Pros Cons
Jasmine
  • Simple setup for node through jasmine-node.
  • Fluent syntax for assertions built-in, and works well with other assertion libraries
  • Supported by many CI servers (TeamCity, Codeship, etc.)
  • Descriptive syntax for BDD paradigm.
  • Asynchronous testing causes a lot of difficulties.
  • Expects a specific suffix to all test files.
Qunit
  • Lots of support across the board, from Q&A to CI server support.
  • Asynchronous testing causes a lot of difficulties.
  • Configuration is really difficult, and must constantly be maintained.
  • Including assertion libraries is difficult.
Mocha
  • Simple setup
  • Supported by some CI servers and plugins for others.
  • Has aliases for functions to be more BDD-oriented or TDD-oriented.
  • Highly extensible.
  • Asynchronous testing is easy.
  • It's relatively new so support might be lacking in certain areas.

Drawbacks

See Also

References

<references/>