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();
Related
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.
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 am trying to create a basic javascript framework that you can pass different things into, including functions for it to execute later. Right now, I'm in a more simple testing phase, but I can't quite get the function calling to work. A piece of my code is here:
[My JS Fiddle][1]http://jsfiddle.net/mp243wm6/
My code has an object that holds different data, and I want to call the method later, but with data that is available at the time of creation. Here is a code snippet of the function that uses the function that is passed to the object:
clickMe : function() {
this.obj.click(function() {
this.func();
});
}
Any suggestions or things I should read are welcome.
The problem is that there're two different contexts:
clickMe : function() {
// here is one
this.obj.click(function() {
// here is another
this.func();
});
}
You can simple pass the function as parameter, like the following:
clickMe : function() {
this.obj.click($.proxy(this.func, this));
}
http://jsfiddle.net/mp243wm6/2/
The problem:
Considering your code in the JSFiddle, you have:
onClick : function() {
this.obj.click(function() {
this.func();
});
},
As noted, you have different contexts going on here.
Consider the snippet this.obj.click(function() { this.func(); }). The first this here is a reference to the framework.events object. The second this here is a reference to whatever will be this when this function get called. In the case of your JSFiddle, when this.func gets called, this is actually the DOM object that represents the <div id="test">TEST</div> node. Since it doesn't have a func function, calling func() on it causes:
Uncaught TypeError: undefined is not a function
You have to understand the following: you have to pass the correct this in which you want the function func to be called.
The solution:
A couple of ways to make it work as you would like:
1. with bind
this.obj.click(this.func.bind(this));
This way, you are telling: "call my this.func function, but make sure that it will be called using the this that I am passing as a parameter". Vanilla JS, no $.proxy stuff.
JSFiddle
2. with a copy of the reference to the actual function
onClick : function() {
var theFunctionReference = this.func;
this.obj.click(function() {
theFunctionReference();
});
},
This way, you will not rely on the value of this outside of the context of the framework.events object.
JSFiddle
The issue is that this is not bound to the correct object. I would suggest you look into Function.bind() because that creates a function with this pointing to the right thing.
I'm using jasmine to test my javascript, and I'm finding a hard case to spec. I have this function which is being passed directly to the handler:
filters.find('#per_page').change(checkList.filter);
Now, I'd like to spy on it and see if it's being called when i change that:
describe("when I change the number of items per page", function() {
beforeEach(function()
spyOn(checkList, 'filter');
$('#per_page').val('50').trigger('change');
});
it("filters the results list", function() {
expect(checkList.filter).toHaveBeenCalled();
});
});
This test fails. Suprisingly, if I change my code above to:
filters.find('#per_page').change(function() { checkList.filter(); });
it passes. Is there a way to spy on such functions passed or do I have to resort to the anonymous function case?
Yes, there is, but to do so events have to be bound after you have added the spies on your function. The reason for this is that spies replace the property with an other function (it doesn't change the function itself). In your case, what happened is that you assigned the original function and when you added the spies you replaced the property without changing the callback that was bound.
If the way your application is made you can't add your spies before doing the events binding, than the only it can work is to use anonymous function which call the proper callback.
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')