Spying on a constructor using Jasmine - javascript

I am using Jasmine to test if certain objects are created and methods are called on them.
I have a jQuery widget that creates flipcounter objects and calls the setValue method on them. The code for flipcounter is here: https://bitbucket.org/cnanney/apple-style-flip-counter/src/13fd00129a41/js/flipcounter.js
The flipcounters are created using:
var myFlipCounter = new flipCounter("counter", {inc: 23, pace: 500});
I want to test that the flipcounters are created and the setValue method is called on them. My problem is that how do I spy on these objects even before they are created? Do I spy on the constructor and return fake objects? Sample code would really help. Thanks for your help! :)
Update:
I've tried spying on the flipCounter like this:
myStub = jasmine.createSpy('myStub');
spyOn(window, 'flipCounter').andReturn(myStub);
//expectation
expect(window.flipCounter).toHaveBeenCalled();
Then testing for the setValue call by flipCounter:
spyOn(myStub, 'setValue');
//expectation
expect(myStub.setValue).toHaveBeenCalled();
the first test for initializing flipCounter is fine, but for testing the setValue call, all I'm getting is a 'setValue() method does not exist' error. Am I doing this the right way? Thanks!

flipCounter is just another function, even if it also happens to construct an object. Hence you can do:
var cSpy = spyOn(window, 'flipCounter');
to obtain a spy on it, and do all sorts of inspections on it or say:
var cSpy = spyOn(window, 'flipCounter').andCallThrough();
var counter = flipCounter('foo', options);
expect(cSpy).wasCalled();
However, this seems overkill. It would be enough to do:
var myFlipCounter = new flipCounter("counter", options);
expect(myFlipCounter).toBeDefined();
expect(myFlipCounter.getValue(foo)).toEqual(bar);

I would suggest using jasmine.createSpyObj() when you want to mock objects with properties that need to be spied on.
myStub = jasmine.createSpyObj('myStub', ['setValue']);
spyOn(window, 'flipCounter').andReturn(myStub);
This tests interactions with the expected flipCounter interface, without depending on the flipCounter implementation.

The following does not rely on 'window'. Lets say this is the code you want to test -
function startCountingFlips(flipCounter) {
var myFlipCounter = new flipCounter("counter", {inc: 23, pace: 500});
}
Your test could be -
var initSpy = jasmine.createSpy('initFlipCounter');
var flipCounter = function(id, options) {
initSpy(id, options);
}
startCountingFlips(flipCounter);
expect(initSpy).toHaveBeenCalledWith("counter", {inc:23, pace:500});

My version to test a constructor is to spy on the prototype:
spyOn(flipCounter.prototype, 'setValue').and.callThrough();
var myFlipCounter = new flipCounter("counter", {inc: 23, pace: 500});
expect(flipCounter.prototype.setValue).toHaveBeenCalledTimes(1);

You have to implement a fake constructor for flipCounter that sets the setValue property to a spy function. Let's say the function you want to test is this:
function flipIt() {
var myFlipCounter = new flipCounter("counter", {inc: 23, pace: 500});
myFlipCounter.setValue(100);
}
Your spec should look like this:
describe('flipIt', function () {
var setValue;
beforeEach(function () {
setValue = jasmine.createSpy('setValue');
spyOn(window, 'flipCounter').and.callFake(function () {
this.setValue = setValue;
});
flipIt();
});
it('should call flipCounter constructor', function () {
expect(window.flipCounter)
.toHaveBeenCalledWith("counter", {inc: 23, pace: 500});
});
it('should call flipCounter.setValue', function () {
expect(setValue).toHaveBeenCalledWith(100);
});
});

Don't know how to do this using jasmine mocks, but if you want powerful mocking/spy/stubs I recommend sinon.js, wich works very well with jasmine.
From docs:
A test spy is a function that records arguments, return value, the
value of this and exception thrown (if any) for all its calls. A test
spy can be an anonymous function or it can wrap an existing function.
Mocks (and mock expectations) are fake methods (like spies) with
pre-programmed behavior (like stubs) as well as pre-programmed
expectations. A mock will fail your test if it is not used as
expected.
With sinon.js you could create a mock of the flipCounter constructor that returns another spy.
Then assert that the constructor was called using constructorMock.calledWithNew(), and assert that the returned spy was called with returnedSpy.calledWith(arg1, arg2...).

Related

How to spyon jquery selector [duplicate]

When it comes to spying on jQuery functions (e.g. bind, click, etc) it is easy:
spyOn($.fn, "bind");
The problem is when you want to spy on $('...') and return defined array of elements.
Things tried after reading other related answers on SO:
spyOn($.fn, "init").andReturn(elements); // works, but breaks stuff that uses jQuery selectors in afterEach(), etc
spyOn($.fn, "merge").andReturn(elements); // merge function doesn't seem to exist in jQuery 1.9.1
spyOn($.fn, "val").andReturn(elements); // function never gets called
So how do I do this? Or if the only way is to spy on init function how do I "remove" spy from function when I'm done so afterEach() routing doesn't break.
jQuery version is 1.9.1.
WORKAROUND:
The only way I could make it work so far (ugly):
realDollar = $;
try {
$ = jasmine.createSpy("dollar").andReturn(elements);
// test code and asserts go here
} finally {
$ = realDollar;
}
Normally, a spy exists for the lifetime of the spec. However, there's nothing special about destroying a spy. You just restore the original function reference and that's that.
Here's a handy little helper function (with a test case) that will clean up your workaround and make it more usable. Call the unspy method in your afterEach to restore the original reference.
function spyOn(obj, methodName) {
var original = obj[methodName];
var spy = jasmine.getEnv().spyOn(obj, methodName);
spy.unspy = function () {
if (original) {
obj[methodName] = original;
original = null;
}
};
return spy;
}
describe("unspy", function () {
it("removes the spy", function () {
var mockDiv = document.createElement("div");
var mockResult = $(mockDiv);
spyOn(window, "$").and.returnValue(mockResult);
expect($(document.body).get(0)).toBe(mockDiv);
$.unspy();
expect(jasmine.isSpy($)).toEqual(false);
expect($(document.body).get(0)).toBe(document.body);
});
});
As an alternative to the above (and for anyone else reading this), you could change the way you're approaching the problem. Instead of spying on the $ function, try extracting the original call to $ to its own method and spying on that instead.
// Original
myObj.doStuff = function () {
$("#someElement").css("color", "red");
};
// Becomes...
myObj.doStuff = function () {
this.getElements().css("color", "red");
};
myObj.getElements = function () {
return $("#someElement");
};
// Test case
it("does stuff", function () {
spyOn(myObj, "getElements").and.returnValue($(/* mock elements */));
// ...
});
By spying on the window itself you have access to any window properties.
As Jquery is one of these you can easily mock it as below and return the value you require.
spyOn(window, '$').and.returnValue(mockElement);
Or add a callFake with the input if it needs to be dynamic.

How can I migrate my custom matchers from Jasmine 1 to Jasmine 2

Version 2 of the JavaScript testing framework jasmine unfortunately introduced several breaking changes. One of these changes is the way custom matchers are handled, as is outlined here:
http://jasmine.github.io/2.0/upgrading.html
The addMatchers function is no longer on the spec (this) it is now on the global jasmine object.
/* was:
this.addMatchers({
*/
jasmine.addMatchers({
A matcher is set up a bit different now. The factory receives a util object which contains things like jasmines equality functions, and any registered customEqualityTesters. The factory is expected to return an object with a compare function which will be called with the actual and expected directly, instead of the actual value being on this
/* was:
toBeCustom: function(expected) {
var passed = this.actual == expected;
*/
toBeCustom: function(util, customEqualityTesters) {
return {
compare: function(actual, expected) {
var passed = actual == expected
The comparison should now return an object with pass and message attributes.
I am looking for an easy way to migrate our existing matchers, so that we can easily switch to the new jasmine version.
To ease the transition to the new jasmine version the following special migration object will help.
Instead of adding the matcher on the this object, you add them on the jasmineMigrate object. But this is really all you need to to. The jasmineMigrate object will take care of the rest.
/* was:
this.addMatchers({
*/
jasmineMigrate .addMatchers({
The implementation of the migration object:
var jasmineMigrate = {};
jasmineMigrate.addMatchers = function (matchers) {
Object.keys(matchers).forEach(function (matcherName) {
var matcher = matchers[matcherName],
migratedMatcher = {};
migratedMatcher[matcherName] = function (util, customEqualityTesters) {
return {
compare: function (actual) {
var matcherArguments,
thisForMigratedMatcher,
matcherResult,
passed;
//In Jasmine 2 the first parameter of the compare function
//is the actual value.
//Whereas with Jasmine 1 the actual value was a property of the matchers this
//Therefore modify the given arguments array and remove actual
matcherArguments = [].slice.call(arguments)
matcherArguments.splice(0, 1);
//Add actual to the this object we'll be passing to the matcher
thisForMigratedMatcher = {
actual: actual
};
//Now call the original matcher aufgerufen, with the modified
//arguments and thisForMigratedMatcher which will be applied to
//the matcher
passed = matcher.apply(thisForMigratedMatcher, matcherArguments);
matcherResult = {
pass: passed,
message: thisForMigratedMatcher.message
};
return matcherResult;
}
}
};
jasmine.addMatchers(migratedMatcher);
});
}
The add-matchers library lets you write matchers which are compatible with Jasmine v1, Jasmine v2, and Jest.

Karma - test function with no return and which sets no scope variable

I've been reading up on karma mainly and jasmine a little and have begun to implement testing on my app.
I have the following function :
$scope.popup1 = function (isinData) {
var popup1 = window.open("views/Box_Ladder.html", "_blank",
"height = 400, width = 700");
shareDataService.setIsinClickValue(isinData);
}
How on earth do I test this using karma? The expected result is a popup window opening and the relevant data being passed to my service. How do I expect this?
You spy on window.open and expect it to be called with the right arguments.
Even if the function doesn't return something, it should at the minimum cause some side-effect. You need to test the side-effects.
To do this, create and inject a mock object + object method. An example would be as follows:
var window = {
open: function(url, target, specs) {
var spec, specKey;
this.href = url;
this.target = target;
// Parse through the spec string to grab the parameters you passed through
var specArray = specs.split(',');
for (specKey in specArray) {
spec = specArray[specKey].split('=');
this[String.trim(spec[0])] = String.trim(spec[1]);
}
}
};
Now you can expect(window.href).toEqual(url), expect(window.target).toEqual(target), expect(window.height).toEqual(400), etc.
Additionally, you need to see if sharedDataService.setIsinClickValue was invoked. If you cannot access this service within your test, you're going to have to create another mock object + method.

How to test that one function is called before another

I have some tightly coupled legacy code that I want to cover with tests. Sometimes it's important to ensure that one mocked out method is called before another. A simplified example:
function PageManager(page) {
this.page = page;
}
PageManager.prototype.openSettings = function(){
this.page.open();
this.page.setTitle("Settings");
};
In the test I can check that both open() and setTitle() are called:
describe("PageManager.openSettings()", function() {
beforeEach(function() {
this.page = jasmine.createSpyObj("MockPage", ["open", "setTitle"]);
this.manager = new PageManager(this.page);
this.manager.openSettings();
});
it("opens page", function() {
expect(this.page.open).toHaveBeenCalledWith();
});
it("sets page title to 'Settings'", function() {
expect(this.page.setTitle).toHaveBeenCalledWith("Settings");
});
});
But setTitle() will only work after first calling open(). I'd like to check that first page.open() is called, followed by setTitle(). I'd like to write something like this:
it("opens page before setting title", function() {
expect(this.page.open).toHaveBeenCalledBefore(this.page.setTitle);
});
But Jasmine doesn't seem to have such functionality built in.
I can hack up something like this:
beforeEach(function() {
this.page = jasmine.createSpyObj("MockPage", ["open", "setTitle"]);
this.manager = new PageManager(this.page);
// track the order of methods called
this.calls = [];
this.page.open.and.callFake(function() {
this.calls.push("open");
}.bind(this));
this.page.setTitle.and.callFake(function() {
this.calls.push("setTitle");
}.bind(this));
this.manager.openSettings();
});
it("opens page before setting title", function() {
expect(this.calls).toEqual(["open", "setTitle"]);
});
This works, but I'm wondering whether there is some simpler way to achieve this. Or some nice way to generalize this so I wouldn't need to duplicate this code in other tests.
PS. Of course the right way is to refactor the code to eliminate this kind of temporal coupling. It might not always be possible though, e.g. when interfacing with third party libraries. Anyway... I'd like to first cover the existing code with tests, modifying it as little as possible, before delving into further refactorings.
I'd like to write something like this:
it("opens page before setting title", function() {
expect(this.page.open).toHaveBeenCalledBefore(this.page.setTitle);
});
But Jasmine doesn't seem to have such functionality built in.
Looks like the Jasmine folks saw this post, because this functionality exists. I'm not sure how long it's been around -- all of their API docs back to 2.6 mention it, though none of their archived older style docs mention it.
toHaveBeenCalledBefore(expected)
expect the actual value (a Spy) to have been called before another Spy.
Parameters:
Name Type Description
expected Spy Spy that should have been called after the actual Spy.
A failure for your example looks like Expected spy open to have been called before spy setTitle.
Try this:
it("setTitle is invoked after open", function() {
var orderCop = jasmine.createSpy('orderCop');
this.page.open = jasmine.createSpy('openSpy').and.callFake(function() {
orderCop('fisrtInvoke');
});
this.page.setTitle = jasmine.createSpy('setTitleSpy').and.callFake(function() {
orderCop('secondInvoke');
});
this.manager.openSettings();
expect(orderCop.calls.count()).toBe(2);
expect(orderCop.calls.first().args[0]).toBe('firstInvoke');
expect(orderCop.calls.mostRecent().args[0]).toBe('secondInvoke');
}
EDIT: I just realized my original answer is effectively the same as the hack you mentioned in the question but with more overhead in setting up a spy. It's probably simpler doing it with your "hack" way:
it("setTitle is invoked after open", function() {
var orderCop = []
this.page.open = jasmine.createSpy('openSpy').and.callFake(function() {
orderCop.push('fisrtInvoke');
});
this.page.setTitle = jasmine.createSpy('setTitleSpy').and.callFake(function() {
orderCop.push('secondInvoke');
});
this.manager.openSettings();
expect(orderCop.length).toBe(2);
expect(orderCop[0]).toBe('firstInvoke');
expect(orderCop[1]).toBe('secondInvoke');
}
Create a fake function for the second call that expects the first call to have been made
it("opens page before setting title", function() {
// When page.setTitle is called, ensure that page.open has already been called
this.page.setTitle.and.callFake(function() {
expect(this.page.open).toHaveBeenCalled();
})
this.manager.openSettings();
});
Inspect the specific calls by using the .calls.first() and .calls.mostRecent() methods on the spy.
Basically did the same thing. I felt confident doing this because I mocked out the function behaviors with fully synchronous implementations.
it 'should invoke an options pre-mixing hook before a mixin pre-mixing hook', ->
call_sequence = []
mix_opts = {premixing_hook: -> call_sequence.push 1}
#mixin.premixing_hook = -> call_sequence.push 2
spyOn(mix_opts, 'premixing_hook').and.callThrough()
spyOn(#mixin, 'premixing_hook').and.callThrough()
class Example
Example.mixinto_proto #mixin, mix_opts, ['arg1', 'arg2']
expect(mix_opts.premixing_hook).toHaveBeenCalledWith(['arg1', 'arg2'])
expect(#mixin.premixing_hook).toHaveBeenCalledWith(['arg1', 'arg2'])
expect(call_sequence).toEqual [1, 2]
Lately I've developed a replacement for Jasmine spies, called strict-spies, which solves this problem among many others:
describe("PageManager.openSettings()", function() {
beforeEach(function() {
this.spies = new StrictSpies();
this.page = this.spies.createObj("MockPage", ["open", "setTitle"]);
this.manager = new PageManager(this.page);
this.manager.openSettings();
});
it("opens page and sets title to 'Settings'", function() {
expect(this.spies).toHaveCalls([
["open"],
["setTitle", "Settings"],
]);
});
});

QUnit, Sinon.js & Backbone unit test frustration: sinon spy appears to fail to detect Backbone Model event callbacks

In the following unit test code:
TestModel = Backbone.Model.extend({
defaults: {
'selection': null
},
initialize: function() {
this.on('change:selection', this.doSomething);
},
doSomething: function() {
console.log("Something has been done.");
}
});
module("Test", {
setup: function() {
this.testModel = new TestModel();
}
});
test("intra-model event bindings", function() {
this.spy(this.testModel, 'doSomething');
ok(!this.testModel.doSomething.called);
this.testModel.doSomething();
ok(this.testModel.doSomething.calledOnce);
this.testModel.set('selection','something new');
ok(this.testModel.doSomething.calledTwice); //this test should past, but fails. Console shows two "Something has been done" logs.
});
The third ok fails, even though the function was effectively called from the backbone event binding, as demo'd by the console.
This is very frustrating and has shaken my confidence on whether sinon.js is suitable for testing my backbone app. Am I doing something wrong, or is this a problem with how sinon detects whether something has been called? Is there a workaround?
EDIT: Here's a solution to my specific example, based on the monkey patch method of the accepted answer. While its a few lines of extra setup code in the test itself, (I don't need the module function any more) it gets the job done. Thanks, mu is too short
test("intra-model event bindings", function() {
var that = this;
var init = TestModel.prototype.initialize;
TestModel.prototype.initialize = function() {
that.spy(this, 'doSomething');
init.call(this);
};
this.testModel = new TestModel();
. . . // tests pass!
});
Calling this.spy(this.testModel, 'doSomething') replaces the testModel.doSomething method with a new wrapper method:
var spy = sinon.spy(object, "method");
Creates a spy for object.method and replaces the original method with the spy.
So this.spy(this.testModel, 'doSomething') is effectively doing something like this:
var m = this.testModel.doSomething;
this.testModel.doSomething = function() {
// Spying stuff goes here...
return m.apply(this, arguments);
};
This means that testModel.doSomething is a different function when you bind the event handler in initialize:
this.bind('change:selection', this.doSomething);
than it is after you've attached your spying. The Backbone event dispatcher will call the original doSomething method but that one doesn't have the Sinon instrumentation. When you call doSomething manually, you're calling the new function that spy added and that one does have the Sinon instrumentation.
If you want to use Sinon to test your Backbone events, then you'll have to arrange to have the Sinon spy call applied to the model before you bind any event handlers and that probably means hooking into initialize.
Maybe you could monkey-patch your model's initialize to add the necessary spy calls before it binds any event handlers:
var init = Model.prototype.initialize;
Model.prototype.initialize = function() {
// Set up the Spy stuff...
init.apply(this, arguments);
};
Demo: http://jsfiddle.net/ambiguous/C4fnX/1/
You could also try subclassing your model with something like:
var Model = Backbone.Model.extend({});
var TestModel = Model.extend({
initialize: function() {
// Set up the Spy stuff...
Model.prototype.initialize.apply(this, arguments);
}
});
And then use TestModel instead of Model, this would give you an instrumented version of Model in TestModel without having to include a bunch of test-specific code inside your normal production-ready Model. The downside is that anything else that uses Model would need to be subclassed/patched/... to use TestModel instead.
Demo: http://jsfiddle.net/ambiguous/yH3FE/1/
You might be able to get around the TestModel problem with:
var OriginalModel = Model;
Model = Model.extend({
initialize: function() {
// Set up the Spy stuff...
OriginalModel.prototype.initialize.apply(this, arguments);
}
});
but you'd have to get the ordering right to make sure that everyone used the new Model rather than the old one.
Demo: http://jsfiddle.net/ambiguous/u3vgF/1/

Categories

Resources