AngularJS Testing a Service with $http and $q - service

I'm new to AngularJS and to JS testing in general and I'm having trouble wrapping my mind around how to go about testing this rather simple service. I've tried using $httpBackend with 'when' and 'expect' GET in variations configurations, to no avail. The test should verify that 1) data is returned via the deferred.resolve and 2) no data is returned via the deferred.reject. If someone could point me in the right direction i'd be quite grateful. Thanks!
btw: I'm using Jasmine + Testacular
.service('myService', function($http, $q) {
return {
getMyData: function() {
var deferred = $q.defer();
$http.get('/foo/bar.do').success(function(data) {
deferred.resolve(data);
}).error(function(){
deferred.reject();
});
return deferred.promise;
}
}
})

I had kind of the same problem testing a service with $http and $q.
Here is one of my test which passes:
it('should issue a GET request to /foo/bar.do', inject(function ($httpBackend) {
$httpBackend.when('GET', '/foo/bar.do').respond('success');
var finalResult = '';
var result = myService.getMyData();
result.then(function(data) {
finalResult = data;
}, function() {
console.log('error');
});
$httpBackend.flush();
expect(finalResult).toBe('success');
}));

Related

executeAsyncScript timing out

I am trying to execute executeAsyncScript using the following code:
function get(url) {
var callback = function(args) {
console.log(args);
};
var defer = protractor.promise.defer();
browser.executeAsyncScript(function (url, callback) {
console.log("url" + url);
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == XMLHttpRequest.DONE) {
console.log(xhr.responseText);
callback(xhr.responseText);
defer.fulfill(xhr);
}
}
xhr.open('GET', url , true);
xhr.send();
}, url);
return defer.promise;
};
function setupCommon() {
return get('https://example.com/rest/api/getsomething');
}
var flow = protractor.promise.controlFlow();
flow.execute(setupCommon);
If I execute the code that is passed to executeAsyncScript directly in the browser console then it works. I get the expected output.
console.log("url" + url);
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == XMLHttpRequest.DONE) {
console.log(xhr.responseText);
callback(xhr.responseText);
defer.fulfill(xhr);
}
}
xhr.open('GET', 'https://example.com/rest/api/getsomething', true);
xhr.send();
But when I execute it using executeAsyncScript, it times out saying:
Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
The restapi shouldn't have taken much time. I am new to all this. I am not sure what I am doing wrong. Can someone please help me with this.
The default timeout for Jasmine is 2000 milli-seconds which looks inadequate in your case as it looks like you indeed have lot of steps
Check the config file reference doc here for different timeout configurations from the protractor.conf.js
You can either increase the timeout at config level as in below
defaultTimeoutInterval: 60000,
allScriptsTimeout:90000
Or increase it for this test case alone
this.timeout(60000)
The default timeout for a script to be executed is 0ms. In most cases, including the examples below, one must set the script timeout WebDriver.Timeouts.setScriptTimeout(long, java.util.concurrent.TimeUnit) beforehand to a value sufficiently large enough.
Here is link for Java API that provides above information
https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/JavascriptExecutor.html#executeAsyncScript-java.lang.String-java.lang.Object...-

protractor - testing rest end point

I am trying to invoke rest end point from Protractor E2E tests. I have referred to following site, however this post seems to be based on old API
eg: protractor.promise.defer(); seems no longer valid on latest API.
http://squirrel.pl/blog/2014/01/15/direct-server-http-calls-in-protractor/
Could you please advise/give sample code to invoke rest end point based on latest protractor API (>= 1.3).
I'm trying to write generic function, something like following.
var executeRequest = function(method, url) {
var defer = protractor.promise.defer();
// method can be ‘GET’, ‘POST’ or ‘PUT’
request({uri: url, method: method, json: true}, function(error, response, body) {
if (error || response.statusCode >= 400) {
defer.reject({
error : error,
message : response
});
} else {
defer.fulfill(body);
}
});
// Return a promise so the caller can wait on it for the request to complete
return defer.promise;
};
You could use request-promise instead. Example from their website...
var rp = require('request-promise');
var options = {
uri : 'http://posttestserver.com/post.php',
method : 'POST'
};
rp(options)
.then(console.dir)
.catch(console.error);
I have used npm module - "needle" to invoke restAPI . That has option for redirects, which helped to validate test site even after redirect.
Please find below sample test to validate rest endpoint is up and running.
it("check restendpoint service is running", function(done){
var restendpointUrl = browser.baseUrl + 'service';
needle.get(healthcheckUrl,{follow_max : 5}, function(err,resp) {
expect(resp.statusCode).toBe(200);
if (!err && resp.statusCode == 200) {
expect(true).toBe(true);
} else {
expect(false).toBe(true);
}
done();
});
});
Brine, many thanks for your earlier reply.

Meteor code must always run within a fiber when deploy in meteor server

I kept having this error when i deploy my app onto meteor cloud server.
Meteor code must always run within a Fiber
at _.extend.get (app/packages/meteor/dynamics_nodejs.js:14:13)
at _.extend.apply (app/packages/livedata/livedata_server.js:1268:57)
at _.extend.call (app/packages/livedata/livedata_server.js:1229:17)
at Meteor.startup.Meteor.methods.streamTwit (app/server/server.js:50:24)
however, I have already wrapped within Fibers
streamTwit: function (twit){
var userid = '1527228696';
twit.stream(
'statuses/filter',
{ follow: userid},
function(stream) {
stream.on('data', function(tweet) {
Fiber(function(){
if(tweet.user.id_str === userid)
{
Meteor.call('addQn', tweet);
}
}).run();
console.log(tweet);
console.log('---------------------------------------------------------');
console.log(tweet.user.screen_name);
console.log(tweet.user.name);
console.log(tweet.text);
});
}
);
}
I don't know what's the reason but someone suggested that i should wrap it with Meteor.bindEnvironment instead. Hence, I did this:
streamTwit: function (twit){
this.unblock(); // this doesn't seem to work
console.log('... ... trackTweets');
var _this = this;
var userid = '1527228696';
twit.stream(
'statuses/filter',
{ follow: userid},
function(stream) {
stream.on('data', function(tweet) {
Meteor.bindEnvironment(function () {
if(tweet.user.id_str === userid)
{
Meteor.call('addQn', tweet);
}
}, function(e) {
Meteor._debug("Exception from connection close callback:", e);
});
console.log(tweet);
console.log('---------------------------------------------------------');
console.log(tweet.user.screen_name);
console.log(tweet.user.name);
console.log(tweet.text);
});
}
);
}
//add question method
addQn:function(tweet){
questionDB.insert({'tweet': tweet, 'date': new Date()});
}
but now it doesn't even work. I realise that this only happened when I tried to insert some data into mongodb.
May I know what is the problem with my code? Thanks!
All these codes were written in app/server/server.js
You shouldn't need to use Meteor.call on the server side. That is for client-side code only. Just call addQn directly or better yet, inline it since it's just one line of code.

AngularJS ng-repeat with data from service

Originally in my app, I created controllers with very basic $http calls to get a resource by getting the ID of an object from the url ($routeParams). Ng-repeat display the results correctly.
However, I noticed refreshing in a later view (different controller) wiped out the data and broke the page. So, I created a function on the service to be used in multiple controllers, to check whether the data has is available and to react as follows:
1) If the resource is defined, return it (no API call)
2) If the resource is not defined, get the id from the url and get it from the API
3) If the resource is not defined & you can't get the ID, just return false.
However, this broke the code: the template rendered before the service returned the data, and ng-repeat did not update. The code looks like this:
angular.module('myApp', ['ngCookies'])
.config(...)
.service('myService', ['$cookies', '$http', function($cookies, $http) {
myData = {};
return {
getData:function(dataID) {
if(myData.name) {return myData);
else if (dataID && dataID !== '') {
$http.get('/api/data/' + dataID)
.success(function(data) {
myData = data.object;
$cookies.dataID = data.object.id;
return myData;
}
}
else { return false; }
}
}
}]);
function myCtrl($scope, $http, $routeParams, myService) {
$scope.data = myService.getData($routeParams.dataID);
...
}
And here's the template. It's in jade, which means rather than angle brackets, you just list the element with parameters in parenthesis right after, and content after the parenthesis.
h2 My heading
ul
li(ng-repeat='option in data')
a(href="#", ng-click='someFuncInCtrl(option.name)') {{ option.name }}
When the controller did the $http.get itself, the ng-repeat worked fine because the $scope was updated in the ".success" callback. Now that there's a service that returns the data after a slight delay, "$scope.data" is just undefined, the ng-repeat list is empty.
I used a console.log to check myData right before return "return myData", and the myData is working, it just isn't returned in time, and for whatever reason the list is not updating whenever $scope does get the data.
I looked a using $routeProvider's resolve... but that makes getting the ID from the url challenging, as the resolve object doesn't seem to have access to $routeParams. I know that $scope.$apply is supposed to help update the scope when it's altered by outside functions... but I have no clue where to put it. The most similar problem on SO didn't use a service.
I tried:
$scope.$apply($scope.data = myService.getData($routeParams.dataID));
And
$scope.$apply(function() {
$scope.data = myService($routeParams.dataID);
});
Both times I only got Error: $digest already in progress.
The problem is on the way you interact with the service. Since your getData function can return both synchronous and/or asynchronous information, you can't just use normal return(s).
$http.get('/api/data/' + dataID)
.success(function(data) {
myData = data.object;
$cookies.dataID = data.object.id;
return myData;
});
The return on the above snippet will not return anything from getData because it will be executed on the context of the $http.get success callback (and not on the getData call stack).
The best approach for handling sync and async service requests is to use promises.
Your getData function should look something like this:
getData:function(dataID) {
var deferred = $q.defer();
if(myData.name) {
deferred.resolve(myData);
} else if (dataID && dataID !== '') {
$http.get('/api/data/' + dataID)
.success(function(data) {
myData = data.object;
$cookies.dataID = data.object.id;
deferred.resolve(myData);
// update angular's scopes
$rootScope.$$phase || $rootScope.$apply();
});
} else {
deferred.reject();
}
return deferred.promise;
}
Note: You need to inject the $rootScope on your service.
And on your controller:
function myCtrl($scope, $http, $routeParams, myService) {
myService.getData($routeParams.dataID).then(function(data) {
// request was successful
$scope.data = data;
}, function() {
// request failed (same as your 'return false')
$scope.data = undefined;
});
}

How to get Meteor.Call to return value for template?

I've tried to understand this post regarding this concept, however, I'm failing to get it. I have the following simple setup:
/server/test.js
Meteor.methods({
abc: function() {
var result = {};
result.foo = "Hello ";
result.bar = "World!";
return result;
}
});
/client/myapp.js
var q = Meteor.call('abc');
console.log(q);
This structure returns to the console undefined.
If I change the myapp.js file to:
Meteor.call('abc', function(err, data) {
!err ? console.log(data) : console.log(err);
}
I receive the Object in my console.
Ideally this is what I'd like to be able to do, but it doesn't work, stating in the console: Cannot read property 'greeting' of undefined
/client/myapp.js
var q = Meteor.call('abc');
Template.hello.greeting = function() {
return q.foo;
}
Any help in passing the data from the server object into the template would be greatly appreciated. I'm still learning JavaScript & Meteor.
Thanks!
From the Meteor.call documentation:
On the client, if you do not pass a callback and you are not inside a stub, call will return undefined, and you will have no way to get the return value of the method. That is because the client doesn't have fibers, so there is not actually any way it can block on the remote execution of a method.
So, you'll want to do it like this:
Meteor.call('abc', function(err, data) {
if (err)
console.log(err);
Session.set('q', data);
});
Template.hello.greeting = function() {
return Session.get('q').foo;
};
This will reactively update the template once the data is available.
This happens because Npm.require has Async behavior. That's the reason that you have to write a callback for Meteor.call.
But there is a solution, just use install(mrt add npm) and you'll get a function named Meteor.sync(//...) with this you can do both games: sync and async in your Meteor.call().
Reference: http://www.sitepoint.com/create-a-meteor-app-using-npm-module/
You can get the return value of a Meteor method for use in a template by using a reactive variable. Check out the working demonstration on Meteorpad
I went for a ghetto solution. But, it works for me, which is what matters, to me. Below is my code, which, in concept, I think, solves OP's problem.
In the client's main.js:
Meteor.setInterval(function() {
confirmLogin();
}, 5000);
This runs the confirmLogin() function every five seconds.
The confirmLogin function (in the client's main.js):
function confirmLogin() {
Meteor.call('loggedIn', function (error, result) {
Session.set("loggedIn", result);
});
}
The loggedIn method (in the server's main.js):
loggedIn: function () {
var toReturn = false;
var userDetails = Meteor.user();
if (typeof userDetails["services"] !== "undefined") {
if (typeof userDetails["services"]["facebook"] != "undefined") {
toReturn = true;
}
}
return toReturn;
},
The relevant helper:
loggedIn: function () {
return Session.get("loggedIn");
}