Jasmine Spies.and.stub method - javascript

I've been reading through the Jasmine documentation and I've been struggling to understand what the Spies .and.stub method actually does. English is not my native language, so I don't even know what the word "stub" actually means, and there is no translation for it in my language.
In the documentation it says:
When a calling strategy is used for a spy, the original stubbing behavior can be returned at any time with and.stub.
describe("A spy", function() {
var foo, bar = null;
beforeEach(function() {
foo = {
setBar: function(value) {
bar = value;
}
};
spyOn(foo, 'setBar').and.callThrough();
});
it("can call through and then stub in the same spec", function() {
foo.setBar(123);
expect(bar).toEqual(123);
foo.setBar.and.stub();
bar = null;
foo.setBar(123);
expect(bar).toBe(null);
});
});
What does and.stub actually do and how is it useful?

For the term, you can look at wikipedia : http://en.wikipedia.org/wiki/Test_stub
In a nutshell it's a "fake" object that you can control that replaces a "real" object in your code.
For the function, what I understand is that and.stub() removes the effect of and.callThrough() on a spy.
When you call and.callThrough, the spy acts as a proxy, calling the real function, but passing through a spy object allowing you to add tests like expectation.
When you call and.stub, or if you never call and.callThrough, the spy won't call the real function. It's really usefull when you don't want to test an object's behavior, but be sure that it was called. Helping you to keep your test truly unitary.

To complete the previous answer:
Indeed, it's not clear from the doc, but it's very clear in the source code:
https://github.com/jasmine/jasmine/blob/4be20794827a63ca5295848c58ffc478624ee4dc/src/core/SpyStrategy.js
plan = function() {};
-> the called function is empty
this.callThrough = function() {
plan = originalFn;
-> the called function is the original function
this.stub = function(fn) {
plan = function() {};
-> the called function is empty (again)

Related

Understanding Sinon Spies: What happens when a spy-wrapped method is called?

When I wrap the method of a class into a Sinon-spy like this:
sinon.spy(myObject, "myMethod")
What happens within the spy?
I guess the spy-object has a reference which points to "myObject.myMethod".
What happens when the method becomes call?
I know that the spy logs information about the invocation like times of invocations, the used parameter etc.
But does the myMethod really become invoked?
I mean: Passes the spy object the invocation further? Does the spy-object act as proxy? Or does it only log the information?
From a simple test it seems that sinon spy does call the original method:
it('does a thing', function() {
const el = {};
el.thing = function() { console.log('thing'); }
sinon.spy(el, 'thing');
el.thing();
console.log(el.thing.called);
});
// prints:
// thing
// true
It also seems like that from the docs:
sinon.spy(object, "method") creates a spy that wraps the existing function object.method. The spy will behave exactly like the original method (including when used as a constructor), but you will have access to data about all calls.

Spying/testing if trigger on event handler works using Chai

I'm very new to Chai testing. I can't find good example. Basically, what I want to happen is check wether the event got triggered.
on my patientInfo.js, the code to trigger the event is
import PatientBus from 'backbone.radio';
patientAdded() {
PatientBus.trigger('patient:added');
},
then on my patientEvents.js
import PatientBus from 'backbone.radio';
this.listenTo(PatientBus, 'patient:added', this.onPatientAdded);
onPatientAdded: function onPatientAdded() {
// blah blah blah
}
Forgot to say, I'm using Marionette radio. The event handler codes above are working great. Now, I want a chai test which will check if the listener of the event received the trigger or broadcast request. I'm not sure where to start and how I will write it.
As #Sgni mentioned, you'll need to spy on your function to 1) know whether or not it was called, and 2) inspect its return value. The Sinon syntax for doing this is:
sinon.spy(yourObject, 'onPatientAdded');
To give you some intuition for how this works, the Sinon docs list this as an example:
function once(fn) {
var returnValue, called = false;
return function () {
if (!called) {
called = true;
returnValue = fn.apply(this, arguments);
}
return returnValue;
};
}
As you can see, it wraps your original function in a closure, which keeps references to your original function's return value, and whether the function was called.
Sinon-Chai just gives you nice syntax, so you can make readable assertions like the following:
expect(yourObject.onPatientAdded).to.have.been.calledOnce;
expect(yourObject.onPatientAdded).to.have.returned(something);
So yes, there is seemingly a lot of magic, but the magic comes from Sinon's use of JavaScript closures and Sinon-Chai's clever use of object properties.

jasmine: scope of this

From the jasmine documentation (http://jasmine.github.io/2.0/introduction.html):
The this keyword
Another way to share variables between a beforeEach, it, and afterEach is through the this keyword. Each spec’s beforeEach/it/afterEach has the this as the > same empty object that is set back to empty for the next spec’s beforeEach/it/afterEach.
My understanding of this in Javascript is, that this is bounded to the scope of the actual function. So I would expect that it would be bound to the context different contexts (the ones depending on the function) inside beforeEach/it/afterEach.
e.g.
describe('Spec', function (){
var eachThis = null;
beforeEach(function(){
eachThis = this;
});
it('check this', function(){
except(this).toEqual(eachThis);
}
};
So this test should pass.
Did jasmine change the behavior of this or did i get something wrong?
I think your example maybe has some issues, but you are correct in thinking that jasmine manipulates the this reference when using beforeEach, beforeAll, etc.
Here is an illustrative example -- note that all of the expects listed below will pass:
(function() {
describe("without beforeEach", function () {
(function() {
// this is not inside of a beforeEach call
this.dog = "Spot";
alert(this.dog);
})();
it("should not have access to a dog property from `this`", function () {
expect(this.dog).toBeUndefined(); // because there is no `dog` member of the object currently referenced by `this`
});
});
describe("a beforeEach test", function () {
beforeEach(function () {
this.dog = "Spot";
});
it("should work now because we used `beforeEach`", function () {
expect(this.dog).toEqual("Spot");
});
});
})();
In general, your thinking about 'this' being defined within the scope of function is correct, but this implementation in jasmine demonstrates how you can provide a specific object to be referenced by the 'this' keyword if you want to. The typical way this is done in javascript is using Function.prototype.apply() which allows you to pass in an arbitrary object for the this reference as the first parameter of the function.
So this test should pass.
Yes, and it does.
Did jasmine change the behavior of this
It doesn't look like it.
did i get something wrong
You have syntax errors and typos in your code (e.g. except -> expect).
Also, toEqual doesn't test the identity of an object, so even expect(this).toEqual({}); would pass. toBe is a better method here.

Strange behaviour delete keyword phantomjs

Context
I have a piece of code that runs through karma with jasmine and phantomjs as a browser, and my problem is that I can't figure out why the delete keyword does not do its job. I looked on phantomjs github to find a hint or a clear documentation about keywords support, in vain.
UPDATE
It actually seems it does not work on chrome either now...
here is the output of the expectation
ShopDataServiceTest should not have an instance method remove FAILED
Expected { getModelName : Function } not to have method 'remove'.
Here is my tested code:
// CRUDService basically returns a new class
// with a prototype containing a method remove
ShopDataService = CRUDService.build(modelName);
delete ShopDataService.prototype.remove;
Here the code that is executed by karma
expect(ShopDataService.prototype).not.toHaveMethod('remove');
Important note
This code is running under karma and does not work in that case, but it works when running on chrome, am I missing something here ?
EDIT I did not mention it before, the expression typeof ShopDataService.prototype.remove returns 'function', as ppoliani pointed out.
Well that's the awkward moment when you realize you prototyped wrong !
Why I was wrong
ShopDataService simply does not have its own property remove, since it is inherited from CRUD which has itself a method remove in its prototype.
I had this
var CRUD = function CRUD(modelName) {
this.getModelName = function () {
return modelName;
};
};
CRUD.prototype = {
save: function () {
// ABSTRACT
},
/**
* Deletes instance from id property
* #return http promise
*/
remove: function () {
// call api
}
};
And now it works with this
var CRUD = function CRUD(modelName) {
this.getModelName = function () {
return modelName;
};
this.save = function () {};
this.remove = function () {};
};
As a side effect, I should not hit the CRUD prototype to define the remove method, but instead define it on the newly created prototype extending CRUD.
If you're running your code in 'strict mode'; it might not allow you to use the delete operator to full extent.

Inheritance and problems with the "this" keyword

I'm building a fairly complex web app that begins with a main menu where the user makes his initial selections. This is the first time I've tried a true OOP approach using inheritance in JavaScript and I've run into my first problem with the "this" keyword not referring to what I expect it to. I'm guessing that it's the result of a broader problem with my OOP/inheritance approach, so I would appreciate an answer that not only tells me how to solve this individual issue, but also provides deeper feedback and advice on my general approach.
I'm only going to post the JS code because I don't think the HTML is relevant, but I can certainly post that as well if necessary.
The following code defines the main class Select. It then creates a subclass of Select called SelectNum (look towards the end of the code). In SelectNum, I'm trying to override the mouseover method of Select, but not entirely -- I want to first call the super's (Select's) method, and then run some additional code. But when this subclass's mouseover method runs, I immediately get the following error:
"Uncaught TypeError: Cannot call method 'stop' of undefined"
Basically, this.shine is undefined.
To start with, I'm using the following code from O'Reilly's JavaScript: The Definitive Guide:
function inherit(p) {
if (Object.create){ // If Object.create() is defined...
return Object.create(p); // then just use it.
}
function f() {}; // Define a dummy constructor function.
f.prototype = p; // Set its prototype property to p.
return new f(); // Use f() to create an "heir" of p.
}
And my code:
Select = function(el){
return this.init(el);
}
Select.prototype = {
init: function(el){
var that = this;
this.el = el;
this.shine = el.children('.selectShine');
el.hover(function(){
that.mouseover();
},function(){
that.mouseout();
});
return this;
},
mouseover: function(){
this.shine.stop().animate({opacity:.35},200);
},
mouseout: function(){
var that = this;
this.shine.stop().animate({opacity:.25},200);
}
}
//Sub-classes
SelectNum = function(el){
this.init(el);
this.sup = inherit(Select.prototype); //allows access to super's original methods even when overwritten in this subclass
return this;
}
SelectNum.prototype = inherit(Select.prototype);
SelectNum.prototype.mouseover = function(){
this.sup.mouseover(); //call super's method... but this breaks down
//do some other stuff
}
EDIT
The response from Raynos worked. this.sup.mouseover() no longer threw the error, and the correct code was run. However, I actually need to create a SelectNum subclass called SelectLevel. Unlike SelectNum that overrides its superclass' mouseover() method, SelectLevel does NOT need to override SelectNum's mouseover() method:
SelectLevel = function(el){
this.init(el);
this.sup = inherit(SelectNum.prototype); //allows access to super's original methods even when overwritten in this subclass
for(var k in this.sup){
this.sup[k] = this.sup[k].bind(this);
}
}
SelectLevel.prototype = inherit(SelectNum.prototype);
With this code, the mouseover() method simply gets called continuously. I believe that's because this is now bound to the SelectLevel object, so this.sup in the line this.sup.mouseover() in SelectNum always refers to SelectNum, so it just keeps calling itself.
If I remove the this.sup[k] = this.sup[k].bind(this); binding in SelectLevel, then I get the error Uncaught TypeError: Cannot call method 'mouseover' of undefined. It appears that this.sup.mouseover() gets called continuously, calling the mouseover method on each object in the prototype chain. When it gets up to Object, that's when this error gets thrown because, of course, Object doesn't have a sup property.
It seems like I can solve this by removing the this.sup[k] = this.sup[k].bind(this); binding in SelectLevel, and then wrapping the this.sup.mouseover() in an if statement that checks first for the sup property before calling the mouseover() method on it: i.e. if(this.sup !== undefined), but this really just doesn't feel right.
Ultimately, I think I'm missing something fundamental about how to subclass in JavaScript. While solutions to these particular issues do shed some light on how prototypal inheritance works in JS, I really think I need a better understanding on a broader level.
this.sup.mouseover();
calls the .mouseover method on the object this.sup. What you want is
this.sup.mouseover.call(this)
You don't want to call it on this.sup you want to call it on this.
If that's a pain in the ass then you can do the following in your constructor
this.sup = inherit(Select.prototype);
for (var k in this.sup) {
if (typeof this.sup[k] === "function") {
this.sup[k] = this.sup[k].bind(this);
}
}
That basically means override every method with the same function but hard bind the value of this to what you expect/want.

Categories

Resources