v3.1 API Developer Guide
Introducing Preamble
Preamble v3 is a powerful BDD based JavaScript testing framework that runs in any modern HTML5 compliant browser as well as headless via PhantomJS. Preamble has no additional dependencies on any other libraries and has a very powerful assertion engine that your test suites interface with through a very simple to use but semantically rich and intuitive API.
This is an example of a synchronous spec:
describe('running a spec synchronously', function(){
it('and running the expectations', function(){
expect(true).toBeTrue();
});
});
And this is an example of an asynchronous spec. Notice the call to done:
describe('running a spec asynchronously', function(){
var count = 0;
it('and calling "done" to run the expectations', function(done){
setTimeout(function(){
count = 100;
done(function(){
expect(count).toEqual(100);
});
}, 1);
});
});
Installing Preamble
To install the Preamble browser-based standalone spec runner on your computer (beginning with v3.1.0):
- From the releases page download the standalone spec-runner for the version of Preamble that you are targeting.
- Create a new folder on your computer and copy the standalone distribution file that you just downloaded to that folder.
- Make the folder you just created the current folder and unzip the standalone distribution.
Run The Sample Test
After you have installed the standalone distro (see Installing Preamble above), you can then run the sample test suite, spec/sample-suite.js, by opening the SpecRunner.html file in your browser. The SpecRunner.html file is located in the standalone distro's root folder.
Running a test suite in the browser produces a report showing the results of running the suite. All suites and specs are presented as links and when you click on them Preamble will run them again and display their details, respectively.
To repeat the test you can either refresh the browser or click on the run all link located near the top left corner of the page.
If you want to filter out suites and specs that have passed, check the Hide passed checkbox located near the top right corner of the page.
After you have run the sample test suite and familiarized yourself with the generated report you can then open up the sample test suite file, spec/sample-suite.js, in your favorite editor and examine the code to gain insight on writing your own test suites.
SpecRunner.html
The only required HTML tags (other than the script tags) are <div id="preamble-test-container"></div> and <div id="preamble-ui-container"></div>.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Preamble</title>
<link href='lib/preamble.css' rel='stylesheet' type='text/css'>
</head>
<body>
<!-- These are required. Do not remove them or rename their ids -->
<div id="preamble-test-container"></div>
<div id="preamble-ui-container"></div>
<!-- ** JavaScript Files Go Here ** -->
<!-- Place configuration file here -->
<!--
<script src="lib/preamble-config.js"></script>
-->
<!-- Place preamble.js here -->
<script src="lib/preamble.js"></script>
<!-- Place scripts that load code that your suite(s) depend on here -->
<!-- <script src="src/your.src.file(s).go.here.js"></script> -->
<!-- Place your suite(s) here -->
<script src="spec/sample-suite.js"></script>
</body>
</html>
When the windowGlobals configuration option is set to false the following API functions must be called as properties of the global Preamble object:
- configure - Preamble.configure
- describe - Preamble.describe
- beforeEach - Preamble.beforeEach
- afterEach - Preamble.afterEach
- it - Preamble.it
- spyOn - Preamble.spyOn
- expect - Preamble.expect
- getUiTestContainerElement - Preamble.getUiTestContainerElement
- getUiTestContainerElementId - Preamble.getUiTestContainerElementId
In the documentation that follows descriptions and code examples assume that the windowGlobals configuration option is set to true.
Suites
describe describe(label, callback)
describe is used to describe a suite which can contain one or more specs. label is a string used to uniquely identify the suite. callback is a function that is called by Preamble that provides structure and scope for one or more specs.
describe('"describe" is used to describe a suite which can contain one or more specs', function(){
it('and "it" is used to describe a spec and is used to group one or more expectations"', function(){
expect(1).toEqual(1);
});
});
Nesting Suites
Suites can be nested at any level thereby providing fine grained structure and organization of your specs:
describe('suites can be nested', function(){
describe('nested suite 1', function(){
it('spec 1.1', function(){
expect(1).toBeTruthy();
});
});
describe('nested suite 2', function(){
it('spec 2.1', function(){
expect(0).not.toBeTruthy();
});
});
});
Specs
it it(label, callback([done]){...}, [timeoutInterval])
it is used to describe a spec and is used to group one or more expectations, which are composed by pairing the actual value under test with an appropriate matcher. label is a string used to uniquely identify the spec within a suite. timeoutInterval is optional and if provided it overrides Preamble's default timeout interval which is the number of miliseconds Preamble waits before timing out a spec (please see timeoutInterval in the Configuration section below for details). callback is a function called by Preamble which contains one or more expectations and which also provides scope to make data and code accessible to expectations.
it('"it" is used to describe a spec and is used to group one or more expectations', function(){
expect(true).toBeTrue();
expect(false).not.toBeTrue();
expect('abc').toEqual('abc');
expect(123).not.toEqual('abc');
});
done is optional and is a function that Preamble passes to callback as an argument which your asynchronous specs call to signal to Preamble that their asynchronous processing has completed. done takes a single argument, a function, which Preamble calls to run the expectations.
describe('specs can be run asynchronously', function(){
var count = 0;
it('and calling done signals to Preamble that the asynchronous process has completed ', function(done){
setTimeout(function(){
count = 100;
done(function(){
expect(count).toEqual(100);
});
}, 1);
});
});
Setup and Teardown
beforeEach beforeEach(callback([done]){...})
afterEach afterEach(callback([done]){...})
beforeEach and afterEach are used to execute common code before and after each spec, respectively, and their use enforces the DRY principle. callback is a function that Preamble will call to execute your setup and teardown code.
describe('Using beforeEach to synchronously execute common code before each test', function(){
var count = 0;
beforeEach(function(){
count = 1;
});
it('count equals 1', function(){
expect(count).toEqual(1);
count = 2;
});
it('count still equals 1', function(){
expect(count).toEqual(1);
});
});
describe('Using afterEach to synchronously execute common code after each test', function(){
var count = 0;
afterEach(function(){
count = 1;
});
it('count equals 0', function(){
expect(count).toEqual(0);
count = 2;
});
it('count still equals 1', function(){
expect(count).toEqual(1);
});
});
done is optional and is a function that Preamble passes to callback as an argument and which your asynchronous setups and teardowns call to signal to Preamble that their asynchronous processing has completed.
describe('Using beforeEach to asynchronously execute common code before each spec is called', function(){
var count = 0;
beforeEach(function(done){
setTimeout(function(){
count = 10;
done();
}, 1);
});
it('count equals 10', function(){
expect(count).toEqual(10);
});
});
describe('Using afterEach to asynchronously execute common code after each spec is called', function(){
var count = 0;
afterEach(function(done){
setTimeout(function(){
count = 1;
done();
}, 1);
});
it('this spec expects count to equal 0 and sets count to 10', function(){
expect(count).toEqual(0);
count = 10;
});
it('this spec expects count to equal 1', function(){
expect(count).toEqual(1);
});
});
Preventing Specs From Timing Out
Preamble will timeout both synchronous and asynchronout specs if they fail to complete within the 50 milisecond timeout interval that Preamble defaults to. To override Preamble's default timeout interval you can:
Set the timeout interval for all specs by modifying the lib/preamble-config.js file
var preambleConfig = {
timeoutInterval: 100
};
or set the timeout interval for all specs using in-line configuration (see Configuration below for details on both).
configure({
timeoutInterval: 100
});
or set the timeout interval for individual specs by passing a timeout interval (an integer) to Preamble when calling it() (see Specs above for details).
describe('Preventing a spec from timing out', function(){
var count = 0;
beforeEach(function(done){
setTimeout(function(){
done(function(){
count = 10;
});
}, 80);
});
it('count should equal 10', function(){
expect(count).toEqual(10);
}, 100);
});
When a spec fails to complete within the timeout interval Preamble will fail that spec but will continue to run all remaining specs unless Preamble is configured to short circuit (see Configuration below for more information about using the shortCircuit configuration option).
Sharing Variables Using this
Variables can be shared between beforeEach, it and afterEach (aka a BIA sequence) by assigning variables to this. Every top level BIA sequence shares the same blank context and every nested BIA sequence shares the same context as their parent BIA sequence.
describe('Sharing values between setups, specs and teardowns using "this"', function(){
beforeEach(function(){
this.value = 10;
});
it('this.value should equal 10', function(){
expect(this.value).toEqual(10);
});
describe('works in nested suites also', function(){
beforeEach(function(){
this.otherValue = 100;
});
it('this.value should equal 10 and this.otherValue should equal 100', function(){
expect(this.value).toEqual(10);
expect(this.otherValue).toEqual(100);
});
});
it('this.otherValue should not exist and this.value should equal 10', function(){
expect(this.otherValue).toEqual(undefined);
expect(this.value).toEqual(10);
});
});
Expectations - expect, not and matchers
Expectations are declared using expect, not if negating, and an appropriate matcher.
expect expect(actual)
Call expect passing it the actual value that is to be matched against the expected value using a matcher. actual can be any valid JavaScript primitive value or object (including functions).
describe('Calling expect', function(){
it('sets the actual value for the expectation', function(){
expect(1).toBeTruthy();
});
});
not not
Use not to negate the intention of a matcher (See Matchers below).
describe('Using not', function(){
it('negates the intention of a matcher', function(){
expect(0).not.toBeTruthy();
});
});
Matchers
toEqual toEqual(value)
Expectations pass if both the actual value and the expected value are equal and fail if they aren't equal. A strict deep recursive comparison is made between the actual value and the expected value, which can be any valid JavaScript primitive value or object (including functions). When comparing objects the comparison is made such that if expected value === actual value and actual value === expected value then the two objects are considered equal.
describe('Calling toEqual', function(){
it('sets the expectation that the actual and expected values are equal' , function(){
var obj1 = {iAm: 'I am!'},
obj2 = {iAm: 'I am!'},
obj3 = {iAm: 'Obj3'};
expect(obj1).toEqual(obj2);
expect(obj2).not.toEqual(obj3);
});
});
toBeTrue toBeTrue()
Expectations pass if the actual value is true and fail if it is false. A strict boolean evaluation is made on the actual value and returns true or false.
describe('Calling toBeTrue', function(){
it('sets the expectation that the actual value is true' , function(){
expect(true).toBeTrue();
expect(false).not.toBeTrue();
});
});
toBeTruthy toBeTruthy()
Expectations pass if the actual value is truthy and fail if it _falsy. A non strict boolean evaluation is made on the actual value and returns true or false.
describe('Calling toBeTruthy', function(){
it('sets the expectation that the actual value is truthy' , function(){
expect(1).toBeTruthy();
expect(0).not.toBeTruthy();
});
});
toHaveBeenCalled toHaveBeenCalled()
Expectations pass if the actual value, which must be a spy (see Spies below), was called and fail if it wasn't called.
describe('Calling toHaveBeenCalled', function(){
it('sets the expectation that the actual value, a spy, was called' , function(){
var spy1 = spyOn(),
spy2 = spyOn();
spy1();
expect(spy1).toHaveBeenCalled();
expect(spy2).not.toHaveBeenCalled();
});
});
toHaveBeenCalledWith toHaveBeenCalledWith(...theArgs)
Expectations pass if the actual value, which must be a spy (see Spies below), was called with the expected ...theArgs arguments and fail if it wasn't called with expected ...theArgs arguments.
describe('Calling toHaveBeenCalledWith', function(){
it('sets the expectation that the actual value, a spy, was called with specific arguments' , function(){
var spy = spyOn();
spy('abc', 'def');
expect(spy).toHaveBeenCalledWith('abc', 'def');
expect(spy).not.toHaveBeenCalledWith('def', 'abc');
});
});
toHaveBeenCalledWithContext toHaveBeenCalledWithContext(context)
Expectations pass if the actual value, which must be a spy (see Spies below), was called with the expected context as its context and fail if it wasn't called with the expected context as its context.
describe('Calling toHaveBeenCalledWithContext', function(){
it('sets the expectation that the actual value, a spy, was called with a specific context' , function(){
var someObject = {
someFn: function(){}
},
someOtherObject = {} ;
spyOn(someObject, 'someFn');
someObject.someFn();
expect(someObject.someFn).toHaveBeenCalledWithContext(someObject);
expect(someObject.someFn).not.toHaveBeenCalledWithContext(someOtherObject);
});
});
toHaveReturned toHaveReturned(value)
Expectations pass if the actual value, which must be a spy (see Spies below), returned the expected value and fail if it didn't return the expected value.
describe('Calling toHaveReturned', function(){
it('sets the expectation that the actual value, a spy, returned a specific value' , function(){
var spy = spyOn().and.return({fName: 'George', lName: 'Washington'});
spy();
expect(spy).toHaveReturned({fName: 'George', lName: 'Washington'});
expect(spy).not.toHaveReturned({fName: 'Washington', lName: 'George'});
});
});
toHaveThrown toHaveThrown()
Expectations pass if the actual value, which must be a spy (see Spies below) or an ordinary function, threw an exception and fail if it didn't throw an exception.
describe('Calling toHaveThrown', function(){
it('sets the expectation that the actual value, a spy, threw an exception', function(){
var someFn = spyOn(function(arg){ return a + arg; }).and.callActual(),
someOtherFn = spyOn(function(arg){ return arg; }).and.callActual();
someFn(20);
someOtherFn('abc');
expect(someFn).toHaveThrown();
expect(someOtherFn).not.toHaveThrown();
});
});
describe('Calling toHaveThrown', function(){
it('sets the expectation that the actual value, an ordinary function, threw an exception', function(){
var someFn = function(i){return i + a;};
expect(function(){
someFn();
}).toHaveThrown();
});
});
toHaveThrownWithMessage toHaveThrownWithMessage(message)
Expectations pass if the actual value, which must be a spy (see Spies below), threw an exception with a specific message and fail if it didn't throw an exception with a specific message.
describe('Calling toHaveThrownWithMessage', function(){
it('sets the expectation that the actual value, a spy, threw an exception with a specific message', function(){
var someFn = spyOn().and.throwWithMessage('Whoops!');
someFn();
expect(someFn).toHaveThrownWithMessage('Whoops!');
expect(someFn).not.toHaveThrownWithMessage('Whoops! That was bad.');
});
});
toHaveThrownWithName toHaveThrownWithName(name)
Expectations pass if the actual value, which must be a spy (see Spies below), threw an exception with a specific name and fail if it didn't throw an exception with a specific name.
describe('Calling toHaveThrownWithName', function(){
it('sets the expectation that the actual value, a spy, threw an exception with a specific name', function(){
var someFn = spyOn().and.throwWithName('Error');
someFn();
expect(someFn).toHaveThrownWithName('Error');
expect(someFn).not.toHaveThrownWithName('MinorError');
});
});
Test Doubles
Preamble provides an assortment of test doubles including spies, stubs, fakes and mocks. Rather than providing separate APIs for each, Preamble encapsulates all of them within its spy implementation.
Spies
Spies are functions and object methods that can track all calls, contexts, arguments and return values.
Creating Spies
Spies are created by calling one of the several forms of spyOn() or by calling **spyOn.x().
spyOn spyOn()
Creates a spy from an anonymous function.
describe('Calling spyOn() without arguments', function(){
it('creates a spy from an anonymous function', function(){
var anonFn = spyOn();
anonFn();
expect(anonFn).toHaveBeenCalled();
});
});
spyOn spyOn(fn)
Creates a spy from the function fn.
describe('Calling spyOn(fn)', function(){
it('creates a spy from the function fn', function(){
var someSpy;
function someFn(){}
someSpy = spyOn(someFn);
someSpy();
expect(someSpy).toHaveBeenCalled();
});
});
spyOn spyOn(object, methodName)
Creates a spy from object[methodName].
describe('Calling spyOn(object, methodName)', function(){
it('creates a spy from object[methodName]', function(){
var someObject = {
someFn: function(){}
};
spyOn(someObject, 'someFn');
someObject.someFn();
expect(someObject.someFn).toHaveBeenCalled();
});
});
spyOn.x spyOn.x(object, methodNames)
Creates a spy from object[methodName] for each methodName found in the array methodNames.
describe('Calling spyOn.x(object, methodNames)', function(){
it('creates a spy from object[methodName] for each methodName found in the array methodNames', function(){
var someObject = {
someFn: function(){},
someOtherFn: function(){}
};
spyOn.x(someObject, ['someFn', 'someOtherFn']);
someObject.someFn();
expect(someObject.someFn).toHaveBeenCalled();
someObject.someOtherFn();
expect(someObject.someOtherFn).toHaveBeenCalled();
});
});
Spy calls API
Information is accumulated for each call to a spy and the calls API can be used to query that information.
calls.count calls.count()
Returns the number of times the spy was called.
describe('Calling calls.count()', function(){
it('returns the number of times the spy was called', function(){
var someFn = spyOn();
someFn();
expect(someFn.calls.count()).toEqual(1);
});
});
calls.forCall calls.forCall(nth)
Returns the ACall object (see ACall API below for details) associated with the nith, an integer, call.
describe('Calling calls.forCall(nth)', function(){
it('returns an ACall object', function(){
var someFn = spyOn(),
aCall;
someFn();
aCall = someFn.calls.forCall(0);
expect(aCall.hasOwnProperty('context')).toBeTrue();
expect(aCall.hasOwnProperty('args')).toBeTrue();
expect(aCall.hasOwnProperty('error')).toBeTrue();
expect(aCall.hasOwnProperty('returned')).toBeTrue();
});
});
calls.all calls.all()
Returns an array of all the ACall objects (see ACall API below for details) associated with the spy.
describe('Calling calls.all()', function(){
it('returns an array of all the ACall objects associated with the spy', function(){
var someFn = spyOn();
someFn();
expect(someFn.calls.all().length).toEqual(1);
});
});
calls.wasCalledWith calls.wasCalledWith(...args)
Returns true if the spy was called with ...args and false if it was not called with ...args.
describe('Calling calls.wasCalledWith(...args)', function(){
it('returns true if the spy was called with args and false if it was not called with args', function(){
var someFn = spyOn();
someFn(123, 'abc', {zip: 55555});
expect(someFn.calls.wasCalledWith(123, 'abc', {zip: 55555})).toBeTrue();
});
});
calls.wasCalledWithContext calls.wasCalledWithContext(object)
Returns true if the spy was called with the context object and false if it was not called with the context object.
describe('Calling calls.wasCalledWithContext(object)', function(){
it('returns true if the spy was called with the context object and false if it was not called with the context object', function(){
var someObj = {
someFn: function(){}
};
spyOn(someObj, 'someFn');
someObj.someFn();
expect(someObj.someFn.calls.wasCalledWithContext(someObj)).toBeTrue();
});
});
calls.returned calls.returned(value)
Returns true if the spy returned value and false if it did not return value.
describe('Calling calls.returned(value)', function(){
it('returns true if the spy returned value and false if it did not return value', function(){
var someObj = {
someFn: function(num){return num;}
};
spyOn(someObj, 'someFn').and.callActual();
someObj.someFn(123);
expect(someObj.someFn.calls.returned(123)).toBeTrue();
});
});
calls.threw calls.threw()
Returns true if the spy threw an exception and false if it did not throw an exception.
describe('Calling calls.threw()', function(){
it('Returns true if the spy threw an exception and false if it did not throw an exception', function(){
var someFn = spyOn().and.throw();
someFn();
expect(someFn.calls.threw()).toBeTrue();
});
});
calls.threwWithMessage calls.threwWithMessage(message)
Returns true if the spy threw an exception with message and false if it did not throw an exception with message.
describe('Calling calls.threwWithMessage()', function(){
it('Returns true if the spy threw an exception with message and false if it did not throw an exception with message', function(){
var someFn = spyOn().and.throwWithMessage('Whoops!');
someFn();
expect(someFn.calls.threwWithMessage('Whoops!')).toBeTrue();
});
});
calls.threwWithName calls.threwWithName(name)
Returns true if the spy threw an exception with name and false if it did not throw an exception with name.
describe('Calling calls.threwWithName()', function(){
it('Returns true if the _spy_ threw an exception with **_name_** and false if it did not throw an exception with **_name_**', function(){
var someFn = spyOn().and.throwWithName('Error');
someFn();
expect(someFn.calls.threwWithName('Error')).toBeTrue();
});
});
reset reset()
Resets a spy back to its default state.
describe('Calling and.reset', function(){
it('resets the spy back to its default state', function(){
var someFn = spyOn();
someFn();
expect(someFn).toHaveBeenCalled();
someFn.and.reset();
expect(someFn).not.toHaveBeenCalled();
});
});
Spy ACall API
An ACall object encapsulates the information pertaining to a single specfic call to a spy and the ACall API can be used to query that information. To obtain an ACall object for a single specific call to a spy call the calls API forCall method (See calls API above).
getContext getContext()
Returns the context that was used for a specific call to the spy.
describe('Calling getContext()', function(){
it('returns the context that was used for a specific call to the _spy_', function(){
var someObject = {
someFn: function(){}
};
spyOn(someObject, 'someFn');
someObject.someFn();
expect(someObject.someFn.calls.forCall(0).getContext()).toEqual(someObject);
});
});
getArgs getArgs()
Returns an Args object (See Args API below) for a specific call to the spy.
describe('Calling getArgs()', function(){
it('returns an Args object for a specific call to the spy', function(){
var someObject = {
someFn: function(){}
};
spyOn(someObject, 'someFn');
someObject.someFn(123);
expect(someObject.someFn.calls.forCall(0).getArgs().args).toEqual([123]);
});
});
getArg getArg(nth)
Works like arguments[nth] for a specific call to the spy.
describe('Calling getArg(nth)', function(){
it('works like arguments[nth] for a specific call to the spy', function(){
var someObject = {
someFn: function(){}
};
spyOn(someObject, 'someFn');
someObject.someFn(123, 456);
expect(someObject.someFn.calls.forCall(0).getArg(0)).toEqual(123);
expect(someObject.someFn.calls.forCall(0).getArg(1)).toEqual(456);
});
});
getArgsLength getArgsLength()
Works like arguments.length for a specific call to the spy.
describe('Calling getArgsLength()', function(){
it('works like arguments.length for a specific call to the spy', function(){
var someObject = {
someFn: function(){}
};
spyOn(someObject, 'someFn');
someObject.someFn(123, 456);
expect(someObject.someFn.calls.forCall(0).getArgsLength()).toEqual(2);
});
});
getArgProperty getArgProperty(nth, propertyName)
Works like arguments[nth][propertyName] for a specific call to the spy.
describe('Calling getProperty(nth, propertyName)', function(){
it('works like arguments[nth][propertyName] for a specific call to the spy', function(){
var someObject = {
someFn: function(){}
};
spyOn(someObject, 'someFn');
someObject.someFn({fName: 'Abraham', lName: 'Lincoln'});
expect(someObject.someFn.calls.forCall(0).getArgProperty(0, 'fName')).toEqual('Abraham');
expect(someObject.someFn.calls.forCall(0).getArgProperty(0, 'lName')).toEqual('Lincoln');
});
});
hasArgProperty hasArgProperty(nth, propertyName)
Works like propertyName in arguments[nth] for a specific call to the spy.
describe('Calling hasArgProperty(nth, propertyName)', function(){
it('works like propertyName in arguments[nth] for a specific call to the _spy_', function(){
var someObject = {
someFn: function(){}
};
spyOn(someObject, 'someFn');
someObject.someFn({fName: 'Abraham', lName: 'Lincoln'});
expect(someObject.someFn.calls.forCall(0).hasArgProperty(0, 'fName')).toBeTrue();
expect(someObject.someFn.calls.forCall(0).hasArgProperty(0, 'lName')).toBeTrue();
expect(someObject.someFn.calls.forCall(0).hasArgProperty(0, 'address')).not.toBeTrue();
});
});
hasArg hasArg(n)
Works like n >= 0 && n < arguments.length for a specific call to the spy.
describe('Calling hasArg(n)', function(){
it('works like n >= 0 && n < arguments.length for a specific call to the spy', function(){
var someObject = {
someFn: function(){}
};
spyOn(someObject, 'someFn');
someObject.someFn('123', 123);
expect(someObject.someFn.calls.forCall(0).hasArg(0)).toBeTrue();
expect(someObject.someFn.calls.forCall(0).hasArg(1)).toBeTrue();
});
});
getError getError()
Returns the error associated with a specific call to the spy.
describe('Calling getError()', function(){
it('returns the error associated with a specific call to the spy', function(){
var someObject = {
someFn: function(number){return number + a;}
};
spyOn(someObject, 'someFn').and.callActual();
someObject.someFn(123);
expect(someObject.someFn.calls.forCall(0).getError()).toBeTruthy();
});
});
getReturned getReturned()
Returns the value returned from a specific call to the spy.
describe('Calling getReturned()', function(){
it('returns the value returned from a specific call to the spy', function(){
var someObject = {
someFn: function(number){return number + 1;}
};
spyOn(someObject, 'someFn').and.callActual();
someObject.someFn(123);
expect(someObject.someFn.calls.forCall(0).getReturned()).toEqual(124);
});
});
Spy Args API
An Args object encapsulates all the arguments passed to a specific call to the spy. To obtain an Args object call the ACall getArgs method (See ACall API above).
getLength getLength()
Works like arguments.length.
describe('Calling getLength()', function(){
it('works like arguments.length', function(){
var someFn = spyOn();
someFn(123, 'abc', {zip: 55555});
expect(someFn.calls.forCall(0).getArgs().getLength()).toEqual(3);
});
});
hasArg hasArg(n)
Works like n >= 0 && n < arguments.length for a specific call to the spy.
describe('Calling hasArg(n)', function(){
it('works like n >= 0 && n < arguments.length for a specific call to the spy', function(){
var someFn = spyOn();
someFn(123, 'abc', {zip: 55555});
expect(someFn.calls.forCall(0).getArgs().hasArg(2)).toBeTrue();
});
});
getArg getArg(n)
Works like arguments[nth].
describe('Calling getArg(n)', function(){
it('works like arguments[nth]', function(){
var someFn = spyOn();
someFn(123, 'abc', {zip: 55555});
expect(someFn.calls.forCall(0).getArgs().getArg(2)).toEqual({zip: 55555});
});
});
hasArgProperty hasArgProperty(nth, propertyName)
Works like propertyName in arguments[nth].
describe('Calling hasArgProperty(nth, propertyName)', function(){
it('works like propertyName in arguments[nth]', function(){
var someFn = spyOn();
someFn(123, 'abc', {zip: 55555});
expect(someFn.calls.forCall(0).getArgs().hasArgProperty(2, 'zip')).toBeTrue();
expect(someFn.calls.forCall(0).getArgs().hasArgProperty(2, 'address')).not.toBeTrue();
});
});
getArgProperty getArgProperty(nth, propertyName)
Works like arguments[nth][propertyName].
describe('Calling getArgProperty(nth, propertyName)', function(){
it('works like arguments[nth][propertyName]', function(){
var someFn = spyOn();
someFn(123, 'abc', {zip: 55555});
expect(someFn.calls.forCall(0).getArgs().getArgProperty(2, 'zip')).toEqual(55555);
});
});
Stubs
Stubs are spies that have predefined behaviors (canned responses) and by default they do not call their actual underlying implementations. Predefine behaviors are added by using the and API.
Stubs API
and.callWithContext and.callWithContext(object)
Instructs the stub to use object as its context (this) when it is called.
describe('Calling and.callWithContext(object)', function(){
it('instructs the stub to use object as its context (this) when it is called', function(){
var context = {},
someFn = spyOn().and.callWithContext(context);
someFn();
expect(someFn).toHaveBeenCalledWithContext(context);
});
});
and.throw and.throw()
Instructs the stub to throw an exception when it is called.
describe('Calling and.throw()', function(){
it('instructs the stub to throw an exception when it is called', function(){
var someFn = spyOn().and.throw();
someFn();
expect(someFn).toHaveThrown();
});
});
and.throwWithMessage and.throwWithMessage(message)
Instructs the stub to throw an exception with message when it is called.
describe('Calling and.throwWithMessage(message)', function(){
it('instructs the stub to throw an exception with message when it is called', function(){
var someFn = spyOn().and.throwWithMessage('Whoops!');
someFn();
expect(someFn).toHaveThrownWithMessage('Whoops!');
});
});
and.throwWithName and.throwWithName(name)
Instructs the stub to throw an exception with name when it is called.
describe('Calling and.throwWithName(name)', function(){
it('instructs the stub to throw an exception with name when it is called', function(){
var someFn = spyOn().and.throwWithName('Error');
someFn();
expect(someFn).toHaveThrownWithName('Error');
});
});
and.return and.return(value)
Instructs the stub to return value when it is called.
describe('Calling and.return(value)', function(){
it('instructs the stub to return value when it is called', function(){
var someFn = spyOn().and.return({zip: 55555});
someFn();
expect(someFn).toHaveReturned({zip: 55555});
});
});
and.callActual and.callActual()
Instructs the stub to call its actual underlying implementation when it is called.
describe('Calling and.callActual()', function(){
it('instructs the stub to call its actual underlying implementation when it is called', function(){
var someFn = function(n){
return n + 1;
},
stub;
stub = spyOn(someFn).and.return(1);
stub(100);
expect(stub).toHaveReturned(1);
stub.and.callActual();
stub(100);
expect(stub).toHaveReturned(101);
});
});
Fakes
Fakes are stubs (and therefore also spies) with fake implementations that can be used as substitutes for expensive dependencies. Fakes_ are created using the and API.
Fakes API
and.callFake and.callFake(fn)
Creates a fake with fn as its implementation.
describe('Calling and.callFake(fn)', function(){
it('creates a fake with fn as its implementation', function(){
var someObject = {
someExpensiveProcess: function(){
.
.
.
return true;
}
};
spyOn(someObject, 'someExpensiveProcess').and.callFake(function(){return true;});
someObject.someExpensiveProcess();
expect(someObject.someExpensiveProcess).toHaveReturned(true);
});
});
Mocks
Mocks are stubs (and therefore also spies) that have predefined expectations and are used to validate behaviors. Add predefined expectations to mocks using the and.expect.it API and validate mocks by calling aMock.validate(). A spec fails if aMock.validate() fails.
Mocks API
and.expect.it.toBeCalled and.expect.it.toBeCalled()
Sets the expectation that the mock must be called.
describe('Calling and.expect.it.toBeCalled()', function(){
it('sets the expectation that the mock must be called', function(){
var aMock = spyOn().and.expect.it.toBeCalled();
aMock();
aMock.validate();
});
});
and.expect.it.toBeCalledWith and.expect.it.toBeCalledWith(...args)
Sets the expectation that the mock must be called with specific arguments ...args.
describe('Calling and.expect.it.toBeCalledWith(...args)', function(){
it('sets the expectation that the mock must be called with ...args', function(){
var aMock = spyOn().and.expect.it.toBeCalledWith('abc', 123, {zip: '55555'});
aMock('abc', 123, {zip: '55555'});
aMock.validate();
});
});
and.expect.it.toBeCalledWithContext and.expect.it.toBeCalledWithContext(object)
Set the expectation that the mock will be called with its context set to object.
describe('Calling and.expect.it.toBeCalledWithContext(object)', function(){
it('sets the expectation that the mock must be called with its context set to object', function(){
var someObject = {
someFn: function(){return this.sayHi();},
sayHi: function(){return 'Hello';}
},
someOtherObject = {
sayHi: function(){return 'Hello World!';}
};
spyOn(someObject, 'someFn').
and.callActual().
and.expect.it.toBeCalledWithContext(someOtherObject).
and.expect.it.toReturn('Hello World!');
someObject.someFn.call(someOtherObject);
someObject.someFn.validate();
});
});
and.expect.it.toReturn and.expect.it.toReturn(value)
Set the expectation that the mock will return value.
describe('Calling and.expect.it.toReturn(value)', function(){
it('sets the expectation that the mock must return value', function(){
var someObject = {
someFn: function(){return {fName: 'Tom', lName: 'Sawyer'};}
};
spyOn(someObject, 'someFn').and.callActual().
and.expect.it.toReturn({fName: 'Tom', lName: 'Sawyer'});
someObject.someFn();
someObject.someFn.validate();
});
});
and.expect.it.toThrow and.expect.it.toThrow()
Set the expectation that the mock must throw an exception when called.
describe('Calling and.expect.it.toThrow()', function(){
it('sets the expectation that the mock must throw an exception when called', function(){
var someObject = {
someFn: function(){ throw new Error('Whoops!');}
};
spyOn(someObject, 'someFn').and.callActual().
and.expect.it.toThrow();
someObject.someFn();
someObject.someFn.validate();
});
});
and.expect.it.toThrowWithName and.expect.it.toThrowWithName(name)
Set the expectation that the mock must throw an exception with name when called.
describe('Calling and.expect.it.toThrowWithName(name)', function(){
it('sets the expectation that the mock must throw an exception with name when called', function(){
var someObject = {
someFn: function(){}
};
spyOn(someObject, 'someFn').and.throwWithName('Error').
and.expect.it.toThrowWithName('Error');
someObject.someFn();
someObject.someFn.validate();
});
});
and.expect.it.toThrowWithMessage and.expect.it.toThrowWithMessage(message)
Set the expectation that the mock must throw an exception with message when called.
describe('Calling and.expect.it.toThrowWithMessage(message)', function(){
it('sets the expectation that the mock must throw an exception with message when called', function(){
var someObject = {
someFn: function(){}
};
spyOn(someObject, 'someFn').and.throwWithMessage('Whoops!').
and.expect.it.toThrowWithMessage('Whoops!'); someObject.someFn();
someObject.someFn.validate();
});
});
UI Tests
Preamble adds the div element with the default id of ui-test-container to the DOM. Use of this element is reserved specifically for UI tests and Preamble itself never adds content to it nor does it ever modify its content. This element's ID can be overridden via configuration (please see Configuration below).
getUiTestContainerElement()
Returns the UI test container DOM element.
var uiTestContainerElement = getUiTestContainerElement();
getUiTestContainerElementId()
Returns the id of the UI test container DOM element.
var elUiTestContainerElement = document.getElementById(getUiTestContainerElementId());
Configuration Using preamble-config.js
The following configuration options can be overridden in the preamble-config.js file located in the lib folder:
windowGlobals
Default value = true. Set to false if you don't want to pollute the global name space and instead want to use the one global variable 'Preamble'.
timeoutInterval
Default value = 50 milliseconds. This is the value Preamble uses to wait for a spec to complete. This value includes the time allocated to setup (beforeEach), teardown (afterEach) and the actual spec.
name
Default value = 'Test'. Override this to display a meaningful name for your suite in the output report.
uiTestContainerId
Default value = 'ui-test-container'. Override this to use a different ID for the UI test container DOM element.
hidePassedTests
Default value = false. Set it to true to hide passed specs.
shortCircuit v2.1.0
Default value = false. Set it to true to instruct Preamble to terminate running any further specs upon the first spec failure. This is a convenient option to use if your suites take a long time to run.
In-line Configuration
Begining with v2.0, you can call configure directly from within your test scripts.
configure configure(hash)
Call configure passing a hash containing the properties and their associated values for the configuration options to be overriden (see Configuration Using preamble-config.js above for descriptions of all the available configuration options).
Place the call to configure at the very top of your test script file.
Please note that the windowGlobals configuration option can only be overriden by setting its value in the preamble-config.js configuration file and that it cannot be overriden using in-line configuration. Please see Configuration Using preamble-config.js above.
//Place the call to configure at the top of your test script file
configure({
name: 'Sample Suite',
hidePassedTests: true,
timeoutInterval: 100
});
.
.
.
Running Headless With PhantomJS
Please note that since v2 Preamble requires PhantomJS v2.0.0 or better.
Please note that if you are installing the PhantomJS v2 binary distribution on a Mac you may need to follow the directions given here.
You can run Preamble headless using PhantomJS. The following example assumes that you already have PhantomJS installed and that it can be found on the path.
- Open up a terminal and change to your spec runner's root folder.
- From the command line enter "path/to/phantomjs lib/phantom-runner.js SpecRunner.html" which should produce output similar to the example below: