Can you have multiple spyObjs in a beforeEach - javascript

I am testing an Angular 1 application with Jasmine. My question is, is it possible to create two spies for two separate services in the same beforeEach statement?
Right now I can get the first spy to work, but I'm not sure why the other spy isn't working. I have the spies setup to where a promise is assigned to a global variable inside of them, which can be accessed in any of the tests. So, the second variable is just returning as undefined instead of the expected promise.
Here is the sample set up code inside the beforeEach:
mockWorkingService = jasmine.createSpyObj('mockWorkingService', ['retrieve']);
mockWorkingService.retrieve.and.callFake(function(crit) {
workingServiceDfr = $q.defer(); // $q is defined globally
return workingService.promise;
});
mockFailingService = jasmine.createSpyObj('mockFailingService', ['retrieve']);
mockFailingService.retrieve.and.callFake(function(crit) {
failingServiceDfr = $q.defer();
return failingService.promise;
});
Also, retrieve is defined separately for each service.

The short answer is "yes", you can have multiple spies in beforeEach statements.

Related

Testing services that return promises using Jasmine 2.4.*

I'm having some issues testing functions return promises in the Jasmine. This solution is very close to the issue that i'm having: How to resolve promises in AngularJS, Jasmine 2.0 when there is no $scope to force a digest?
The post is from 2014 and it's not clear what version of jasmine they are using. Is this still the correct why to test a function that resolves a promise in the current (2.4.0^) version?
Edit:
I have a service whose pattern looks something like this:
angular.module('somemodule').factory('listOfDependencies','NewService');
function NewService(listOfDependencies){
var getObject = function(para1, para2,...,paraN){
var deferred = $q.defer();
if(someDependency.method())
deferred.resolve(someDependency.get(key));
else
deferred.resolve(returnsNewObject);
// there's also a case that returns deferred.reject(reason);
return deferred.promise;
};
return getObject:getObject;
};
In my spec, the test currently looks something like this
it('should return the object', inject(function() {
var obj = { some: Object };
NewService.getObject('param1'...'paramN').then(
function(data){
expect(data.obj).toEqual(obj);
},
function(response){
//promise failed
});
Now what I expect to be returned based on the object 'obj' should pass. In my service it's this case it should logically return"
if(someDependency.method())
deferred.resolve(someDependency.get(key));
The problem is that is that the object it returns is:
else
deferred.resolve(returnsNewObject);
There's nothing wrong the the logic in the code or any its dependencies (I pulled all of this apart and tested it many time) so I feel like something is wrong in my syntax(?) in the jasmine spec or i'm just not testing the promise correctly. Thanks for taking a look at this!
Resolving the promise and then checking that the values it resolves to are valid and expected are definitely one way of doing it.
If you're ok with using the Chai library, it's a lot more concise:
http://chaijs.com/plugins/chai-as-promised/
To make your code easier to unit test, try doing as much of the heavy lifting as possible in Angular services (as opposed to in the controllers), and have the services return promises. That way your unit tests can call the service methods with various inputs and ensure the promises are rejected/resolved as expected.
I resolved this issue by calling $rootScope.$apply(); at the end of my test.

currentSpec in Jasmine 2 is undefined

I want to upgrade my test suite to the latest Jasmine version 2.3.4. I have some custom helper methods for testing AngularJS stuff inside my spy_helper.js like this:
(function() {
this.stubPromise = function(service, functionName) {
var $q = jasmine.getEnv().currentSpec.$injector.get("$q")
var $rootScope = jasmine.getEnv().currentSpec.$injector.get("$rootScope")
var deferred = $q.defer();
var spy = spyOn(service, functionName).andReturn(deferred.promise);
spy.andResolveWith = function(value) {
deferred.resolve(value);
$rootScope.$apply();
return spy;
};
spy.andRejectWith = function(value) {
deferred.reject(value);
$rootScope.$apply();
return spy;
};
return spy;
};
}).call(this);
// inside a test you can do
stubPromise(someService, 'foobar').andResolveWith("test");
The helper stubs a promise and immediately resolves or rejects the promise. I have a bunch of such helpers.
The problem with Jasmine 2 is, that jasmine.getEnv().currentSpec is undefined. This is intended by the Jasmine 2 developers, as far as I know.
But how could I adapt my helpers for Jasmine 2? I could extend the arguments to get the $injector or even $q and $rootScope, but that would result in unhandy tests like stubPromise(someService, 'foobar', $injector).andResolveWith("test"); AND you would have to inject the $injector on top of every test.
So, is there a way to get angular's $injector inside my helpers?
Furthermore in some other helpers, I need to catch the after method of a spec, to check if expected spies on promises where called jasmine.getEnv().currentSpec.after(flush);
Any help is appreciated, because those helpers saved me a lot of time, and I'd be not very happy to rewrite all of them or throw them out. Maybe there is also a library out there, which does something similar (and I didn't know it).

The promise of a promise again (Angular JS)

Updated with HTTP and initial code based on requests/Please look at the bottom of the post:
I've been posting several questions on my AngularJS learning curve of late and the SO community has been fantastic. I've been a traditional C programmer when I used to program and have recently started writing my own ionic/Angular JS app. I'm struggling with the promise version of traditional async calls when it comes to converting a custom function to a promise. I don't think I really understood and I find various examples very contrived. I'd appreciate some help. I have some code which is not working, and I have some conceptual questions:
Let's take this simple function:
angular.module('zmApp.controllers').service('ZMDataModel', function() { return { getMonitors: function () { return monitors; } }
getMonitors is a simple function that basically returns an array of monitors. But here is the rub: When the app first starts, I call an http factory that does an http get and goes about populating this monitor list. This http factory is different from this service but invokes a setMonitor method in this service to populate the array. When the array is populated, a variable called 'monitorsLoaded' is set to 1. When this variable is set to 1, I know for sure monitors is loaded.
Now, I have a view with a controller called "MontageCtrl". I want to wait for the monitors to load before I show the view. In a previous post, one person suggested I use route resolve, but I had to first convert my getMonitors to a promise. So here is what I did:
angular.module('zmApp.controllers').service('ZMDataModel', function($q) {
getMonitors: function () {
var _deferred = $q.defer();
if (monitorsLoaded!=0)
{
console.log ("**** RETURNING MONITORS *****");
_deferred.resolve(monitors);
}
console.log ("*** RETURNING PROMISE ***");
return _deferred.promise;
},
Next up, in app.js I connected the route as follows:
.state('app.montage', {
data: {requireLogin:false},
resolve: {
message: function(ZMDataModel)
{
console.log ("Inside app.montage resolve");
return ZMDataModel.getMonitors();
}
},
Finally I modified my controller to grab the promise as such:
angular.module('zmApp.controllers').controller('zmApp.MontageCtrl', function($scope,$rootScope, ZMHttpFactory, ZMDataModel,message) {
//var monsize =3;
console.log ("********* Inside Montage Ctrl");
It seems based on logs, I never go inside Montage Ctrl. Route resolve seems to be waiting for ever, whereas my logs are showing that after a while, monitorLoaded is being set to 1.
I have several conceptual questions:
a) In function getMonitors, which I crafted as per examples, why do people return a _deferred.promise but only assign a _deferred.resolve? (i.e. why not return it too?). Does it automatically return?
b) I noticed that if I moved var _deferred definition to my service and out of its sub function, it did work, but the next view that had the same route dependency did not. I'm very confused.
c) Finally I ready somewhere that there is a distinction between a service and a factory when it comes to route resolve as a service is only instantiated once. I am also very confused as in some route resolve examples people use when, and I am using .state.
At this stage, I'm deep into my own confusion. Can someone help clarify? All I really want is for various views to wait till monitorsLoaded is 1. And I want to do it via route resolves and promises, so I get the hang of promises once and for all.
Added: Here is the HTTP factory code as well as the app.run code that calls this when the app first starts. FYI, the http factory works well - the problems started when I crafted ZMDataModel - I wanted this to be a central data repository for all controllers to use -- so they did not have to call HTTP Factory each time to access data, and I could control when HTTP factory needs to be called
angular.module('zmApp.controllers').factory('ZMHttpFactory', ['$http', '$rootScope','$ionicLoading', '$ionicPopup','$timeout','ZMDataModel',
function($http, $rootScope, $ionicLoading, $ionicPopup, $timeout,ZMDataModel) {
return {
getMonitors: function() {
var monitors = [];
var apiurl = ZMDataModel.getLogin().apiurl;
var myurl = apiurl+"/monitors.json";
return $http({
url: myurl,
method: 'get'
}) //http
.then(function(response) {
var data = response.data;
//console.log("****YAY" + JSON.stringify(data));
// $rootScope.$broadcast ('handleZoneMinderMonitorsUpdate',monitors);
$ionicLoading.hide();
ZMDataModel.setMonitors(data.monitors);
ZMDataModel.setMonitorsLoaded(1);
//monitors = data.monitors;
return ZMDataModel.getMonitors();
},
function (result)
{
console.log ("**** Error in HTTP");
$ionicLoading.hide();
ZMDataModel.setMonitorsLoaded(1);
//$ionicPopup.alert ({title: "Error", template:"Error retrieving Monitors. \nPlease check if your Settings are correct. "});
return ZMDataModel.getMonitors();
}
); //then
}, //getMonitors
And here is the code in app.run that first calls this:
.run(function($ionicPlatform, $ionicPopup, $rootScope, $state,ZMDataModel, ZMHttpFactory)
{
ZMDataModel.init();
var loginData = ZMDataModel.getLogin();
if ( loginData.username && loginData.password && loginData.url && loginData.apiurl)
{
console.log ("VALID CREDENTIALS. Grabbing Monitors");
// this calls http factory getMonitors that eventually populated the ZMDataModel
// monitors array and sets monitorsLoaded to 1
ZMHttpFactory.getMonitors();
}
}
I finally solved all the problems. There were various issues with my initial attempts. My final resolved solution is here Am I returning this promise correctly?
The learnings:
a) Separating the HTTP get into a factory and the data model into another service was unnecessarily complicating life. But that separation was not the problem. Infact, the way the promise was coded above, on first run, if monitorsLoaded was 0, it would simply return the deferred promise and there was no ".success" or similar construct for me to get into the resolve code block again.
b) The biggest thing that was making me run around in loops was deferring or rejecting was simply setting a state. the return always has to be the promise - and it would return the state you set. I assumed return d.promise always means returning "in progress".

AngularJS promises and nested controllers

I have the following dilemma:
If I have two nested controllers like this:
Controller1
Controller2
In the Controller1 I have a field
someDataService.getMyUser().then(function(user){
$scope.user = user;
}
which is the result of a promise (an async call to external server).
So far, so good. But how can I manage the following situation:
I need to use in the Controller2
var userName = $scope.$parent.user.userName
right when the controller is fired.
How can I set the second controller, to wait for the promise in the first controller to be resolve, and only then evaluate that var userName assignment?
(otherwise I would get that $scope.$parent is undefined)
Instead of calling $scope.$parent.user.userName, can't you call a function (something like $scope.$parent.get_user()) that calls someDataServive.getMyUser() and returns a promise?
Or, isn't better to inject someDataService into Controller2 and call directly the getMyUser() function?
It's not really good to have a dependency like this, but if you need to call some init code in Controller2, you can do it using $broadcast to run init code of Controller2 directly from Controller1 when the object will be loaded.

Angular.js promise not resolving when unit testing service with karma

I am trying to unit test an Angular.js service, and need to set an expect on a promise returned from a Mock service (using Jasmine). I am using the karma unit testing framework. The relevant code snippet is below:
// I can't figure out how to do the equivalent of a $scope.$digest here.
var loginStatusPromise = FacebookService.getFacebookToken();
loginStatusPromise.then(function(token) {
expect(false).toBeTruthy(); // If this test passes, there is something going wrong!
expect(token).not.toBeNull(); // The token should be ValidToken
expect(token).toBe('ValidToken');
});
The complete unit test code can be seen here.
The problem is the promise.then statement never fires when karma is executing. Hence, none of my expect statements are executed.
In my controller tests, I use $scope.$digest() to resolve the promises, but I am not clear on how to do this in a service test. As I thought there was no notion of 'scope' in a service test.
Do I have the wrong end of the stick here? Do I need to injecct $rootScope into my service test and then use $digest? Or, is there another way?
I had this problem and resolved it by simply putting a
$rootScope.$apply() at the end of my test
Your FacebookService might be the issue, as suggested by #mpm. Are you sure it doesn't have any http calls happening inside of that Facebook dependency which wouldn't be occurring during unit testing? Are you certain that resolve has been called on the deferred yet?
Assuming that you are using ngFacebook/ngModule a quick note before the solution/ideas is that this project does not have unit tests ! Are you sure you want to use this project ?
I did a quick scan of your Unit Tests on Github and found following missing:-
1) Module initialization.
ngFacebook needs that or you need to initialize your module that does the same thing.
beforeEach(module('ngFacebook'));
OR
beforeEach(module('yieldtome'));
2) Seriously consider mocking ngFacebook module
At unit level tests you are testing your code within a mocked bubble where outside interfaces are stubbed out.
Otherwise) Try adding calling the API as below:-
$rootScope.$apply(function() {
this.FacebookService.getFacebookToken().then(function(){
//your expect code here
});
});
$httpBackend.flush();//mock any anticipated outgoing requests as per [$httpBackend][2]
beforeEach(function(){
var self=this;
inject(function($rootScope,Facebook){
self.$rootScope=$rootScope;
self.Facebook=Facebook;
});
})
it('resolves unless sourcecode broken',function(done){
// I can't figure out how to do the equivalent of a $scope.$digest here.
var loginStatusPromise = this.FacebookService.getFacebookToken();
loginStatusPromise.then(function(token) {
expect(token).toBe('ValidToken');
done();
});
$rootscope.$apply();
});
https://docs.angularjs.org/api/ng/service/$q
I agree with the above answers that a service should have nothing to do with $rootScope.
In my case had a $q promise, that used a second service internally resolving to a promise as well. No way to resolve the external one, unless I added $rootScope.$digest() into my service code (not the test)...
I ended-up writing this quick shim for $q to use in my tests, but be careful, as it's just an example and not a complete $q implementation.
beforeEach(module(function($provide) {
$provide.value('$q', {
defer: function() {
var _resolve, _reject;
return {
promise: {
then: function (resolve, reject) {
_resolve = resolve;
_reject = reject;
}
},
resolve: function (data) {
window.setTimeout(_resolve, 0, data);
},
reject: function (data) {
window.setTimeout(_reject, 0, data);
}
};
}
});
}));
Hope it will be useful to someone, or if you have any feedback.
Thanks.

Categories