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.
Related
var obj = {};
obj.localContext = 'firstTemp';
obj.call = function(){
obj.localContext = 'secondTemp';
};
Jasmine-spec:
it('value of localContext', function(){
spyOn(obj, 'call');
obj.call();
expect(obj.localContext).toEqual('secondTemp');
});
Why is the obj.call() method never being called? When I run the spec, the value of obj.localContext is still firstTemp instead of secondTemp
When you create a spy, the default behaviour is to replace the object with a mock that doesn't call the original. Typically you'd use it to test functionality that would otherwise call out to other APIs you don't want to be called - you can test that they would have been called, without actually calling them.
Jasmine does provide you a way to also call the original function though:
spyOn(obj, "call").and.callThrough();
See the Jasmine documentation for spies (unfortunately, linking directly to the and.callThrough section doesn't work)
I have following angular service.
(function () {
"use strict";
angular.module("theModule")
.factory("theService", theServiceFactory);
function theServiceFactory() {
return {
theFn: theFn,
theFailureFn: theFailureFn,
theSuccessFn: theSuccessFn
};
function theFn() {
angular.noop();
}
function theFailureFn() {
theFn();
}
function theSuccessFn() {
this.theFn();
}
}
}());
Functions are defined separately and their references are assigned to object being returned by the factory.
And I have following jasmine test cases.
describe("The Service Test Specifications", function () {
var theService;
beforeEach(module('theModule'));
beforeEach(inject(function(_theService_) {
theService = _theService_;
}));
it("should call 'theFn' from 'theFailureFn'", function () {
spyOn(theService, "theFn");
theService.theFailureFn();
expect(theService.theFn).toHaveBeenCalled();
});
it("should call 'theFn' from 'theSuccessFn'", function () {
spyOn(theService, "theFn");
theService.theSuccessFn();
expect(theService.theFn).toHaveBeenCalled();
});
});
Test Case should call 'theFn' from 'theFailure' is being failed whereas should call 'theFn' from 'theSuccess' is being passed.
From source code it seems object's theFn is referring to function theFn but actually it's not. It is causing first test case to fail. (Q1) At what stage different reference is assigned to object's theFn?
Second test case is passing as theFn is invoked with this inside theSuccess. But using this in that situation is strict mode violation. I like and follow John Papa's style guide so I created factory and defined all functions below it. (Q2) What would be the better way to write such function?
(Q3) If theFailure is written correctly, is there any way to detect in test case that function theFn is called?
Plunkr: http://plnkr.co/edit/PsQTZQlIgWI3OTMaYi7n
It is actually calling theFn() from theFailureFn(), and NOT calling theFn() from the sucesssFn(). It's the opposite result of your test.
As you see from this,
http://plnkr.co/edit/Tp5FtsL8DAjkcPO0m0ZV?p=preview
console.log('theService.theFn BEFORE spyOn', theService.theFn);
spyOn(theService, "theFn");
console.log('theService.theFn AFTER spyOn', theService.theFn);
Result
theService.theFn BEFORE spyOn theFn()
theService.theFn AFTER spyOn j$.createSpy.spy()
your Jasmine spyOn(theService, "theFn"); is setting an instance of theService, this.theFn, and your test is checking this.theFn is called or not. Remember that theService is a Hash, not a function.
As you see from the output of this line;
console.log('caller theFailureFn', this, theFn, this.theFn)
//OUTPUT
caller theFailureFn Object {} theFn() j$.createSpy.spy()
theFn and this.theFn is very different. theFn is an object property value and this.theFn is an instance of object, Hash.
To answer your question,
(Q1) At what stage different reference is assigned to object's theFn
theFn is assigned as you expected. spyOn makkes difference in your case.
(Q2) What would be the better way to write such function?
To test a function calls a function of an object, it should return a function, not a hash.
(Q3) If theFailure is written correctly, is there any way to detect in test case that function theFn is called?
The same answer as Q2.
Dont use this in .factory(). Factory is just executed as function and it just returns what you explicitly specify as return object. Angular uses .service() as a constructor function which is executed with new operator and thats the place where you will use this.
That means that your theFailureFn() is written correctly.
As for Q3, it may fail just because how spyOn is implemented.
Edit:
As i supposed, its the implementation of spyOn().
spyOn wraps the function but in your factory you are still referencing the original function. Try to use expect(this.theFn).toHaveBeenCalled();
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.
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)
I looked at the other questions regarding spying on functions in Jasmine but I didn't get my doubt answered there. I intend to use andCallThrough to track my original function in my src script. This is what I have:
describe("My Test to spy :", function() {
var mySpy = jasmine.createSpy(window, "login");
beforeEach(function(){
mySpy();
});
it("Expects login() will be called", function(){
expect(mySpy).toHaveBeenCalled();
});
});
So this test passes because its the spy that is being called right? Not the original implementation of the function. So if I use mySpy.andCallThrough() it gives an error. The docs are all about chaining objects and for methods. Nothing for functions. Need some help.
The problem is that you use createSpy instead of spyOn. createSpy will create a new spy so you can't use andCallThrough on it as there is no function to call. Using spyOn will replace an existing function with the spy and save the old function in the spy. So when you use andCallThrough it will call this old method.
You can use createSpy but then you have to pass a name and the original function:
jasmine.createSpy('someName', window.login)
When you use `spyOn', you have to pass the an object holding the function and the name of the function:
jasmine.spyOn(window, 'login')