I have an angular service class : -
angular.module('triggerTips')
.service('userData', function ($rootScope, $http, $log, $firebase) {
this._log = {
service : 'userData'
};
// Synchronized objects storing the user data
var config;
var userState;
// Loads the user data from firebase
this.init = function(readyCallback) {
var log = angular.extend({}, this._log);
log.funct = 'init';
var fireRef = new Firebase('https://XYZfirebaseio.com/' + $rootScope.clientName);
config = $firebase(fireRef.child('config')).$asObject();
userState = $firebase(fireRef.child('userState').child($rootScope.userName)).$asObject();
Promise.all([config.$loaded(), userState.$loaded()]).
then(
function() {
if(config == null || Object.keys(config).length < 4) {
log.message = 'Invalid config';
$log.error(log);
return;
}
if(!userState.userProperties) {
userState.userProperties = {};
}
if(!userState.contentProperties) {
userState.contentProperties = {};
}
log.message = 'User Properties: ' + JSON.stringify(userState.userProperties);
$log.debug(log);
log.message = 'Content Properties: ' + JSON.stringify(userState.contentProperties);
$log.debug(log);
log.message = 'Loaded user data from firebase';
$log.debug(log);
readyCallback();
},
function() {
log.message = 'Unable to load user data from firebase';
$log.error(log);
}
);
};
// Returns the initial tip configuration
this.getConfig = function() {
return config;
};
// Set the value of a user property
// A user property is something about the user himself
this.setUserProperty = function(property, value) {
if(!userState.userProperties) {
userState.userProperties = {};
}
userState.userProperties[property] = value;
userState.$save();
$rootScope.$broadcast('user-property-change', property);
};
// Get the value of a user property
this.getUserProperty = function(property) {
if(userState.userProperties) {
return userState.userProperties[property];
}
};
// Set the value of a user content property
// A content property is something about a particular peice of content for a particular user
this.setContentProperty = function(contentName, property, value) {
if(!userState.contentProperties[contentName]) {
userState.contentProperties[contentName] = {};
}
userState.contentProperties[contentName][property] = value;
userState.$save();
$rootScope.$broadcast('content-property-change', contentName, property);
};
// Increment a count property on the user state for a given tip
this.incrementContentProperty = function(contentName, property) {
if(!userState.contentProperties[contentName]) {
userState.contentProperties[contentName] = {};
}
if(!userState.contentProperties[contentName][property]) {
userState.contentProperties[contentName][property] = 0;
}
userState.contentProperties[contentName][property]++;
userState.$save();
$rootScope.$broadcast('content-property-change', contentName, property);
};
// Returns the user state for a given tip and property
this.getContentProperty = function(contentName, property) {
if(userState.contentProperties) {
var t = userState.contentProperties[contentName];
if(t) {
return t[property];
}
}
};
});
I am trying to unit test this service using jasmine:-
my unit test is :-
'use strict';
describe('Service: userData', function () {
// load the service's module
beforeEach(function() {
module('triggerTips');
});
// instantiate service
var userData;
beforeEach(inject(function (_userData_) {
userData = _userData_;
}));
it('should load correctly', function () {
expect(!!userData).toBe(true);
});
describe('after being initialized', function () {
beforeEach(function(done) {
// Unable to get this working because the callback is never called
userData.init(function() {
done();
});
jasmine.DEFAULT_TIMEOUT_INTERVAL = 2000;
});
it('should have a valid config', function (done) {
setTimeout(function() {
expect(Object.keys(userData.getConfig()).length == 0);
done();
}, 1500);}); }); });
I read about the Asynchronous Support in Jasmine, but as I am rather new to unit testing with JavaScript couldn't make it work.
I am receiving an error :
Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL
Can somebody help me providing working example of my code with some explanation?
I would suggest that you replace setTimeout
with $timeout
so as to speed up your spec suite. You will need ngMock to be a part of your spec suite in order to get this working the intended way, but that seems to have already been taken care of looking at your spec. Good stuff.
Then in order to make the async nature of the spec "go away" you would call:
$timeout.flush([delay]) where delay
is optional.
With this, you can remove the done
callback and write your tests as such:
describe('after being initialized', function () {
var $timeout;
beforeEach(function () {
// Unable to get this working because the callback is never called
userData.init();
inject(function ($injector) {
$timeout = $injector.get('$timeout');
});
}));
it('should have a valid config', function () {
$timeout.flush();
// callback should've been called now that we flushed().
expect(Object.keys(userData.getConfig()).length).toEqual(0);
});
});
What Promise
implementation are you using? I see a call to Promise.all
but for the sake of continuing on with my answer I'm going to assume it is equivalent to $q.all
. Running $timeout.flush
should take care of resolving those values.
If you want to write expectations on the rejected/resolved values of a promise in Jasmine, I would look into something like jasmine-promise-matchers to make it clean and pretty, but barring that you could do something like this:
// $q
function get () {
var p1 = $timeout(function () { return 'x'; }, 250);
var p2 = $timeout(function () { return 'y'; }, 2500);
return $q.all([p1, p2]);
}
// expectation
it('is correct', function () {
var res;
get().then(function (r) {
res = r;
});
$timeout.flush(2500);
expect(res).toEqual(['x', 'y']);
});
Depending on your setup, you may or may not have to stub out/spy on (depending on your frameworks definition of a spy) the promise in relation to your local config
variable, but that's another story altogether I reckon.
I am not at all familiar with $firebase(something).$asObject.$loaded - as such I may have missed something here, but assuming it works 'just like any other promise' you should be good to go.
jsfiddle
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With