introduction.js |
|
---|---|
Jasmine is a behavior-driven development framework for testing JavaScript code. It does not depend on any other JavaScript frameworks. It does not require a DOM. And it has a clean, obvious syntax so that you can easily write tests. This guide is running against Jasmine version FILLED IN AT RUNTIME. |
|
Suites:
|
describe("A suite", function() {
it("contains spec with an expectation", function() {
expect(true).toBe(true);
});
});
|
It's Just FunctionsSince |
describe("A suite is just a function", function() {
var a;
it("and so is a spec", function() {
a = true;
expect(a).toBe(true);
});
});
|
ExpectationsExpectations are built with the function |
describe("The 'toBe' matcher compares with ===", function() {
|
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 |
it("and can have a negative case", function() {
expect(false).not.toBe(true);
});
});
|
Included MatchersJasmine has a rich set of matchers included. Each is used here - all expectations and specs pass. |
describe("Included matchers:", function() {
it("The 'toBe' matcher compares with ===", function() {
var a = 12;
var b = a;
expect(a).toBe(b);
expect(a).not.toBe(null);
});
describe("The 'toEqual' matcher", function() {
it("works for simple literals and variables", function() {
var a = 12;
expect(a).toEqual(12);
});
it("should work for objects", function() {
var foo = {
a: 12,
b: 34
};
var bar = {
a: 12,
b: 34
};
expect(foo).toEqual(bar);
});
});
it("The 'toMatch' matcher is for regular expressions", function() {
var message = 'foo bar baz';
expect(message).toMatch(/bar/);
expect(message).toMatch('bar');
expect(message).not.toMatch(/quux/);
});
it("The 'toBeDefined' matcher compares against `undefined`", function() {
var a = {
foo: 'foo'
};
expect(a.foo).toBeDefined();
expect(a.bar).not.toBeDefined();
});
it("The `toBeUndefined` matcher compares against `undefined`", function() {
var a = {
foo: 'foo'
};
expect(a.foo).not.toBeUndefined();
expect(a.bar).toBeUndefined();
});
it("The 'toBeNull' matcher compares against null", function() {
var a = null;
var foo = 'foo';
expect(null).toBeNull();
expect(a).toBeNull();
expect(foo).not.toBeNull();
});
it("The 'toBeTruthy' matcher is for boolean casting testing", function() {
var a, foo = 'foo';
expect(foo).toBeTruthy();
expect(a).not.toBeTruthy();
});
it("The 'toBeFalsy' matcher is for boolean casting testing", function() {
var a, foo = 'foo';
expect(a).toBeFalsy();
expect(foo).not.toBeFalsy();
});
describe("The 'toContain' matcher", function() {
it("works for finding an item in an Array", function() {
var a = ["foo", "bar", "baz"];
expect(a).toContain("bar");
expect(a).not.toContain("quux");
});
it("also works for finding a substring", function() {
var a = "foo bar baz";
expect(a).toContain("bar");
expect(a).not.toContain("quux");
});
});
it("The 'toBeLessThan' matcher is for mathematical comparisons", function() {
var pi = 3.1415926, e = 2.78;
expect(e).toBeLessThan(pi);
expect(pi).not.toBeLessThan(e);
});
it("The 'toBeGreaterThan' is for mathematical comparisons", function() {
var pi = 3.1415926, e = 2.78;
expect(pi).toBeGreaterThan(e);
expect(e).not.toBeGreaterThan(pi);
});
it("The 'toBeCloseTo' matcher is for precision math comparison", function() {
var pi = 3.1415926, e = 2.78;
expect(pi).not.toBeCloseTo(e, 2);
expect(pi).toBeCloseTo(e, 0);
});
it("The 'toThrow' matcher is for testing if a function throws an exception", function() {
var foo = function() {
return 1 + 2;
};
var bar = function() {
return a + 1;
};
expect(foo).not.toThrow();
expect(bar).toThrow();
});
});
|
Writing a custom matcherThere is also the ability to write custom matchers for when a project's domain calls for specific assertions that are not included. |
describe("Adding a custom matcher", function() {
beforeEach(function() {
this.addMatchers({
toBeGoofy: function(expected) {
|
|
if (expected === undefined) {
expected = '';
}
|
|
var pass = this.actual.hyuk === "gawrsh" + expected;
|
Failure MessagesIf left |
if (pass) {
|
The matcher succeeded, so the custom failure message should be present in the case of a negative expectation - when the expectation is used with |
this.message = "Expected " + this.actual + " not to be quite so goofy";
} else {
|
The matcher failed, so the custom failure message should be present in the case of a positive expectation |
this.message = "Expected " + this.actual + " to be goofy, but it was not very goofy";
}
|
Return whether or not the comparison passed |
return pass;
}
});
});
|
Once a custom matcher is registered with Jasmine, it is available on any expectation. |
it("is available on an expectation", function() {
expect({
hyuk: 'gawrsh'
}).toBeGoofy();
});
it("can take an 'expected' parameter", function() {
expect({
hyuk: 'gawrsh is fun'
}).toBeGoofy(' is fun');
});
it("can be negated", function() {
expect({
hyuk: 'this is fun'
}).not.toBeGoofy();
});
});
|
Grouping Related Specs with
|
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 TeardownTo help a test suite DRY up any duplicated setup and teardown code, Jasmine provides the global Here is the same set of specs written a little differently. The variable under test is defined at the top-level scope -- the |
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);
});
});
|
Nesting
|
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 Specs and SuitesSuites and specs can be disabled with the |
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);
});
});
|
SpiesJasmine's test doubles are called spies. A spy can stub any function and tracks calls to it and all arguments. There are special matchers for interacting with spies. The |
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 its number of calls", function() {
expect(foo.setBar.calls.length).toEqual(2);
});
it("tracks all the arguments of its calls", function() {
expect(foo.setBar).toHaveBeenCalledWith(123);
expect(foo.setBar).toHaveBeenCalledWith(456, 'another param');
});
it("allows access to the most recent call", function() {
expect(foo.setBar.mostRecentCall.args[0]).toEqual(456);
});
it("allows access to other calls", function() {
expect(foo.setBar.calls[0].args[0]).toEqual(123);
});
it("stops all execution on a function", function() {
expect(bar).toBeNull();
});
});
|
Spies:
|
describe("A spy, when configured to call through", function() {
var foo, bar, fetchedBar;
beforeEach(function() {
foo = {
setBar: function(value) {
bar = value;
},
getBar: function() {
return bar;
}
};
spyOn(foo, 'getBar').andCallThrough();
foo.setBar(123);
fetchedBar = foo.getBar();
});
it("tracks that the spy was called", function() {
expect(foo.getBar).toHaveBeenCalled();
});
it("should not affect other functions", function() {
expect(bar).toEqual(123);
});
it("when called returns the requested value", function() {
expect(fetchedBar).toEqual(123);
});
});
|
Spies:
|
describe("A spy, when faking a return value", function() {
var foo, bar, fetchedBar;
beforeEach(function() {
foo = {
setBar: function(value) {
bar = value;
},
getBar: function() {
return bar;
}
};
spyOn(foo, 'getBar').andReturn(745);
foo.setBar(123);
fetchedBar = foo.getBar();
});
it("tracks that the spy was called", function() {
expect(foo.getBar).toHaveBeenCalled();
});
it("should not affect other functions", function() {
expect(bar).toEqual(123);
});
it("when called returns the requested value", function() {
expect(fetchedBar).toEqual(745);
});
});
|
Spies:
|
describe("A spy, when faking a return value", function() {
var foo, bar, fetchedBar;
beforeEach(function() {
foo = {
setBar: function(value) {
bar = value;
},
getBar: function() {
return bar;
}
};
spyOn(foo, 'getBar').andCallFake(function() {
return 1001;
});
foo.setBar(123);
fetchedBar = foo.getBar();
});
it("tracks that the spy was called", function() {
expect(foo.getBar).toHaveBeenCalled();
});
it("should not affect other functions", function() {
expect(bar).toEqual(123);
});
it("when called returns the requested value", function() {
expect(fetchedBar).toEqual(1001);
});
});
|
Spies:
|
describe("A spy, when created manually", function() {
var whatAmI;
beforeEach(function() {
whatAmI = jasmine.createSpy('whatAmI');
whatAmI("I", "am", "a", "spy");
});
it("is named, which helps in error reporting", function() {
expect(whatAmI.identity).toEqual('whatAmI');
});
it("tracks that the spy was called", function() {
expect(whatAmI).toHaveBeenCalled();
});
it("tracks its number of calls", function() {
expect(whatAmI.calls.length).toEqual(1);
});
it("tracks all the arguments of its calls", function() {
expect(whatAmI).toHaveBeenCalledWith("I", "am", "a", "spy");
});
it("allows access to the most recent call", function() {
expect(whatAmI.mostRecentCall.args[0]).toEqual("I");
});
});
|
Spies:
|
describe("Multiple spies, when created manually", function() {
var tape;
beforeEach(function() {
tape = jasmine.createSpyObj('tape', ['play', 'pause', 'stop', 'rewind']);
tape.play();
tape.pause();
tape.rewind(0);
});
it("creates spies for each requested function", function() {
expect(tape.play).toBeDefined();
expect(tape.pause).toBeDefined();
expect(tape.stop).toBeDefined();
expect(tape.rewind).toBeDefined();
});
it("tracks that the spies were called", function() {
expect(tape.play).toHaveBeenCalled();
expect(tape.pause).toHaveBeenCalled();
expect(tape.rewind).toHaveBeenCalled();
expect(tape.stop).not.toHaveBeenCalled();
});
it("tracks all the arguments of its calls", function() {
expect(tape.rewind).toHaveBeenCalledWith(0);
});
});
|
Matching Anything with
|
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));
});
});
});
|
Mocking the JavaScript ClockThe Jasmine Mock Clock is available for test suites that need the ability to use |
describe("Manually ticking the Jasmine Mock Clock", function() {
var timerCallback;
|
It is installed with a call to |
beforeEach(function() {
timerCallback = jasmine.createSpy('timerCallback');
jasmine.Clock.useMock();
});
|
Calls to any registered callback are triggered when the clock is ticked forward via the |
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.callCount).toEqual(1);
jasmine.Clock.tick(50);
expect(timerCallback.callCount).toEqual(1);
jasmine.Clock.tick(50);
expect(timerCallback.callCount).toEqual(2);
});
});
|
Asynchronous SupportJasmine also has support for running specs that require testing asynchronous operations. |
describe("Asynchronous specs", function() {
var value, flag;
it("should support async execution of test preparation and expectations", function() {
|
Specs are written by defining a set of blocks with calls to |
runs(function() {
flag = false;
value = 0;
setTimeout(function() {
flag = true;
}, 500);
});
|
The The latch function polls until it returns true or the timeout expires, whichever comes first. If the timeout expires, the spec fails with the error message. |
waitsFor(function() {
value++;
return flag;
}, "The Value should be incremented", 750);
|
Once the asynchronous conditions have been met, another |
runs(function() {
expect(value).toBeGreaterThan(0);
});
});
});
|
The Runner and ReporterJasmine is built in JavaScript and must be included into a JS environment, such as a web page, in order to run. Like this web page. This file is written in JavaScript and is compiled into HTML via Rocco. The JavaScript file is then included, via a Scroll down the page to see the results of the above specs. All of the specs should pass. Meanwhile, here is how a runner works to execute a Jasmine suite. |
(function() {
var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 250;
|
Create the |
var htmlReporter = new jasmine.HtmlReporter();
jasmineEnv.addReporter(htmlReporter);
|
Delegate filtering of specs to the reporter. Allows for clicking on single suites or specs in the results to only run a subset of the suite. |
jasmineEnv.specFilter = function(spec) {
return htmlReporter.specFilter(spec);
};
|
Run all of the tests when the page finishes loading - and make sure to run any previous Test ResultsScroll down to see the results of all of these specs. |
var currentWindowOnload = window.onload;
window.onload = function() {
if (currentWindowOnload) {
currentWindowOnload();
}
document.querySelector('.version').innerHTML = jasmineEnv.versionString();
execJasmine();
};
function execJasmine() {
jasmineEnv.execute();
}
})();
|
Downloads
Support
ThanksRunning documentation inspired by @mjackson and the 2012 Fluent Summit. |
|