I am wondering how callbacks works in angularJS.
I have this code working perfectly like this
$scope.loginFB = function () {
hello(FACEBOOK).login(function () {
hello(FACEBOOK).api('me').then(function (profile) {
console.log('successful api call');
dbService.handle_credentials(profile);
$rootScope.$apply(function () {
$location.path('/homePage');
});
}, function(){
console.error('something went wrong with authentification');
});
});
};
but works in weird way when refactored like this
$scope.loginHandler =function () {
hello(FACEBOOK).api('me').then(function (profile) {
console.log('successful api call');
dbService.handle_credentials(profile);
$rootScope.$apply(function () {
$location.path('/homePage');
});
}, function(){
console.error('something went wrong with authentification');
});
};
$scope.loginFB = function () {
hello(FACEBOOK).login($scope.loginHandler());
};
please tell me what i am doing wrong with this refactoring.
By including the params, you are immediately invoking your function callback rather than passing a function reference, which is what you really want to do.
$scope.loginFB = function () {
hello(FACEBOOK).login($scope.loginHandler);
};
If you want to pass a parameter to your callback function, you can use one of two approaches.
Wrap your callback in an anonymous function
$scope.loginFB = function () {
hello(FACEBOOK).login(function() { return $scope.loginHandler(param); });
};
In a modern browser, use .bind().
$scope.loginFB = function () {
hello(FACEBOOK).login($scope.loginHandler.bind(this, param)));
};
Related
I have a class in jquery that calls a service and returns a promise, which is then executed in my main area via a .done and Im trying to wrap that call in a another class I have that will make sure multiple calls are not made for the same ID. However I am finding this very very difficult to test as I can not accurcately get the promise working in phantomJS/Sinon. Heres what the area Im trying to test is
LOCKER.execute(diagRunId, function (unlock) {
SERVICE_ACCESSOR.makeCall params)
.done(function (data) {
console.log("Success!");
unlock();
})
.fail(function (jqXHR, textStatus, errorThrown) {
console.log("Failed!");
unlock();
});
});
and In my test file I have my setup like so
var setup = function() {
P.mock('service-accessor', function () {
return {
makeCall: sinon.stub().returns({})
};
});
P.mock('locker', function () {
var methods = {
execute: function (lockId, wrapped) {
console.log('locked - ' + lockId)
wrapped()
},
unlock: sinon.stub()
};
return {
execute: methods.execute,
unlock: methods.unlock
};
});
P.start();
}
with finally the test just calling the method
aui.suite('Locker Test', function () {
aui.test('should lock and then unlock', testFile, {
setup: setup,
browser: function () {
P.when('utils', 'service-accessor','locker').execute(
'test', function (SERVICE_ACCESSOR, LOCKER) {
UTILS.makeCall("Identifier")
expect(LOCKER.unlock).to.have.been.called.once;
done();
}
);
},
validate: function () {},
});
});
The locker works and begins execution of the service call, but then the service call fails with
Error: PhantomJS: `TypeError: 'undefined' is not a function (evaluating 'SERVICE_ACCESSOR.callService( params).done')` near
L2492> }).fail(function (jqXHR, textStatus, errorThrown) {
From my understanding my mock should return a just a empty object when its being called, but then I dont understand a) Why its going to fail and b) Whats undefined? My assumption is that its because Im not returning three objects, but Im trying to get it to succeed first! How can I correctly stub/mock this?
In the end I didn't make a promise or use a stub. I used the following function that would call the done and fail in my call instead.
function() {
return { done: function(callback) {
if(window.makeParamountCallSuccess) {
callback({"data": "data"});
return {
fail: function(){}
}
} else {
return {
fail: function(failCallback){ failCallback("jqXHR", "textStatus", "errorThrown")}
}
}
}
}
}```
I have a function that makes an AJAX call to a service. I'm attempting to expect that the displayError function is called on a failure.
I have my function ajaxCall that accepts a url. Upon success I pass the result to displaySuccess and when there's an error I pass the details to displayError.
function ajaxCall(url) {
$.ajax({
method: "GET",
url: url,
data: "json",
beforeSend: function (xhr) {
//Do Stuff
},
error: function(xhr, textStatus, errorThrown) { displayError(xhr, textStatus, errorThrow, url)},
success: function (results) { displaySuccess(result) }
});
}
function displayError(xhr, textStatus, errorThrow, url) {
//Do Stuff//
}
function displaySuccess(results) {
//Do Stuff//
}
In Jasmine I have it successfully verifying the URL. My problem is in testing to insure that the displayError and displaySuccess functions are called.
I have the following for this specific issue so far.
describe('The ajaxCall component', function() {
it('should call the error function when the ajax call fails', function () {
var obj = {};
spyOn(obj, 'displayError');
spyOn($, "ajax").and.callFake(function (options) {
options.error();
});
ajaxCall('/myResource/get');
expect(obj.method).toHaveBeenCalled();
});
}
I'm a little new to unit testing and I've searched trying to find suggestions that would help but they make the unit test fail. Where am I going wrong with this?
This all boils down to how you spy on your objects and writing code that is more testable. Let's work through a few strategies.
Strategy 1
Given your current code is not within an object, you could test that these functions are called by simply testing their implementation directly.
Instead of testing that the functions were called, you would test their implementation directly.
Example
describe("strategy 1", function () {
var ajaxSpy;
beforeEach(function () {
ajaxSpy = spyOn($, 'ajax');
ajaxCall();
});
describe("error callback", function () {
beforeEach(function() {
spyOn(window, 'alert');
var settings = ajaxSpy.calls.mostRecent().args[0];
settings.error();
});
describe("when there is an error", function() {
it("should alert an error message", function() {
expect(window.alert).toHaveBeenCalledWith('Error');
});
});
});
});
Strategy 2
While the above works, it can be cumbersome to write tests. Ideally, you want to test the invocation and implementation separately.
To do so, we can spy on these functions. Since these are in the global namespace, you can spy on them through the window object.
Example
describe("strategy 2", function () {
var ajaxSpy;
beforeEach(function () {
ajaxSpy = spyOn($, 'ajax');
ajaxCall();
});
describe("error callback", function () {
beforeEach(function() {
spyOn(window, 'displayError');
var settings = ajaxSpy.calls.mostRecent().args[0];
settings.error();
});
describe("when there is an error", function() {
it("should alert an error message", function() {
expect(window.displayError).toHaveBeenCalled();
});
});
});
});
Strategy 3 (Recommended)
The final strategy, and what I recommend, has a similar setup to the second strategy, except we encapsulate our implementation into a custom object.
Doing so makes the code more testable by wrapping functionality in objects and avoids the global namespace (i.e. window).
Example
describe("solution 3", function() {
var ajaxSpy;
beforeEach(function() {
ajaxSpy = spyOn($, 'ajax');
ajaxService.ajaxCall();
});
describe("error callback", function() {
beforeEach(function() {
spyOn(ajaxService, 'displayError');
var settings = ajaxSpy.calls.mostRecent().args[0];
settings.error();
});
it("should alert an error message", function() {
expect(ajaxService.displayError).toHaveBeenCalled();
});
});
});
I am trying to run a Meteor.js application using scrollTo(0,0) to get each of my iron-routes to go to the top of the page:
client/lib/router.js:
Router.route('/services', {
name: 'services',
template: 'services',
onAfterAction: function () {
scrollTop();
}
});
Router.route('/inquiry', function() {
this.layout('inquiry');
onAfterAction: function () {
scrollTop();
}
});
function scrollTop() {
window.scrollTo(0, 0);
}
Console error:
meteor.js:880 Exception in callback of async function: TypeError: window.scrollTo is not a function
at scrollTop (http://localhost:3000/app/client/lib/router.js?942b5705d17b5d736fe545b9dd17f3ea42238776:45:12)
at Router.route.onAfterAction (http://localhost:3000/app/client/lib/router.js?942b5705d17b5d736fe545b9dd17f3ea42238776:17:9)
at RouteController.runHooks (http://localhost:3000/packages/iron_router.js?c564289eeaa191561eba900052037432ebfcbe4a:265:7)
at RouteController._runRoute (http://localhost:3000/packages/iron_router.js?c564289eeaa191561eba900052037432ebfcbe4a:551:8)
at Function.Route.dispatch (http://localhost:3000/packages/iron_router.js?c564289eeaa191561eba900052037432ebfcbe4a:848:18)
at route (http://localhost:3000/packages/iron_router.js?c564289eeaa191561eba900052037432ebfcbe4a:705:11)
at boundNext (http://localhost:3000/packages/iron_middleware-stack.js?3370bd57ef7b310cca3f5dddb11b77fafdcfc1eb:418:31)
at http://localhost:3000/packages/meteor.js?9730f4ff059088b3f7f14c0672d155218a1802d4:999:22
at dispatch (http://localhost:3000/packages/iron_middleware-stack.js?3370bd57ef7b310cca3f5dddb11b77fafdcfc1eb:442:3)
at http://localhost:3000/packages/iron_router.js?c564289eeaa191561eba900052037432ebfcbe4a:385:13
Your second route is incorrect, you've mixed the functional style setup with the object style of i-r.
Router.route('/inquiry', function() {
this.layout('inquiry');
onAfterAction: function () {
scrollTop();
}
});
Should be:
Router.route('/inquiry', {
template: 'inquiry',
onAfterAction: function () {
scrollTop();
}
});
Except the syntax bug like #Michel said, you might need to have
onAfterAction: function () {
if (this.ready()) {
scrollTop();
}
}
to ensure that the template is rendered
This error seemed to go away when I used .scroll(0,0) instead of .scrollTo(0,0). I am not sure why this is the case, but the error is gone and the functionality is there using scroll
I am trying to learn the Jasmine spyOn function. I get TypeError: Cannot read property 'fail' when using spyOn to test a function that calls jQuery.getJSON.
Here is the function I want to test:
getJsonServerNine: function () {
'use strict';
$.getJSON(queryServer.url, function(simpleJson) {
parseResponse(simpleJson);
}).fail(function(error) {
console.log(error.statusText);
});
}
Here is my test:
it("tests getJSON call", function() {
spyOn($, 'getJSON').and.callFake(function (url, success) {
success({
"nine": 9
});
});
queryServer.getJsonServerNine();
expect(queryServer.queryResult).toBe(9);
});
If I remove the fail callback from getJsonServerNine() then my code works. If I call jquery.ajax() instead of getJSON then I can find a way to get it to work. How can can I get it to work with a call to $.getJSON?
I have seen this answer to a similar question and it is very nice but does not closely match my case, does not work for me, and uses outdated Jasmine syntax.
The object returned by $.getJSON should have a fail() method. In your implementation it returns undefined.
The fake function should return an object like so:
// Source
var queryServer = {
getJsonServerNine: function () {
'use strict';
$.getJSON(queryServer.url, function (simpleJson) {
// parseResponse(simpleJson);
console.log(simpleJson)
queryServer.queryResult = simpleJson[Object.keys(simpleJson)[0]];
}).fail(function (error) {
console.log(error.statusText);
});
}
}
// Test
describe('foo', function () {
it("tests getJSON call", function () {
spyOn($, 'getJSON').and.callFake(function (url, success) {
success({
"nine": 9
});
return {
fail: function() {}
}
});
queryServer.getJsonServerNine();
expect(queryServer.queryResult).toBe(9);
});
});
See JS fiddle here - http://jsfiddle.net/eitanp461/32e17uje/
So I have something like this:
var Utils = {};
Utils.genericAddRowPost = function(url) {
return $.post(url);
};
Utils.genericAddRow = function(dataSource, url) {
genericAddRowPost(url).done(function(data, textStatus, jqXHR) {
// add on to dataSource and other stuff
}).fail(function (jqXHR, textStatus, errorThrown) {
//handle error
});
};
I am attempting to test and achieve 100% code coverage using jasmine and blanket, but I can't seem to be able to mock/execute the done and fail handlers. Any help would be greatly appreciated. I would prefer not to have to restructure any of the code posted if possible.
Using Jasmine, you should be able to spy on your ajax calls and simulate success and failure conditions.
Something like this:
describe("my tests", function () {
beforeEach(function () {
spyOn(jQuery, "ajax");
});
it("should handle success", function () {
genericAddRow(a, b);
// call the success callback
$.ajax.mostRecentCall.args[1].success(data, textStatus, jqXHR);
// do your tests here
});
it("should handle failure", function () {
genericAddRow(a, b);
// call the success callback
$.ajax.mostRecentCall.args[1].error(jqXHR, textStatus, errorThrown);
// do your tests here
});
});
So here is what I did:
it('Verify Utils.genericAddRow', function () {
var wasSuccessful = false;
mockObj = {
data: ko.observableArray([{}])
};
// spy on genericAddRowPost that is called inside this test function
spyOn(Utils, "genericAddRowPost").andReturn({
done: function (callback) {
callback({});
wasSuccessful = true;
return this;
},
fail: function (callback) {
return this;
}
});
// Call our test function and make first set of expectations
Utils.genericAddRow(mockObj, 'fakeUrl');
expect(Utils.genericAddRowPost).toHaveBeenCalledWith('fakeUrl');
expect(wasSuccessful).toBeTruthy();
// Override original spy implementations
Utils.genericAddRowPost().done = function (callback) {
return this;
};
Utils.genericAddRowPost().fail = function(callback) {
callback(null, null, 'testError');
wasSuccessful = false;
return this;
};
// Call our test function and make second set of expectations
Utils.genericAddRow(mockObj, 'anotherFakeUrl');
expect(Utils.genericAddRowPost).toHaveBeenCalledWith('anotherFakeUrl');
expect(wasSuccessful).toBeFalsy();
});
I will edit my question to reflect that genericAddRow and genericAddRowPost are both functions that live on a Utils object literal.