How to test an Angular Factory which uses Parse SDK - angularjs

This is my first question here. I'll try to do my best. I searched a lot before posting
I'm developing an angularJS application relying on the Javascript ParseSDK.
I have convinced myself to dive in testing recently, so I am a beginner.
I have this factory UserFactory that wraps around the SDK so everything is clean and modular the Angular way. ie: SDK is only used through factories (not controller nor directives).
It goes like this:
myModule.factory('UserFactory', ['$q', function($q){
var User = Parse.User.extend({
// Instance methods
},{
// static/class methods
// Overrides Parse.User.current() to wrap current User in promise
current: function(){
var deferred = $q.defer();
var currentUser = Parse.User.current();
if(currentUser)
deferred.resolve(currentUser);
else
deferred.reject("No current user");
return deferred.promise;
}
});
return User;
}]);
My question: How to test for UserFactory.current() knowing it uses an external service?
I've looked into mocking the Parse SDK but don't know how to do it since it's not angular related (ie: can't use httpBackend).
My current test file:
describe("Unit: UserFactory", function(){
var UserFactory;
beforeEach(function(){
module("myModule");
inject(function(_UserFactory_){
UserFactory = _UserFactory_;
});
});
it("should return current User", function(){
// What to expect ?
});
});
Thank you in advance

describe("Unit: UserFactory", function(){
var UserFactory;
beforeEach(function(){
module("myModule");
inject(function(_UserFactory_){
UserFactory = _UserFactory_;
$rootScope = _$rootScope_;
});
});
describe('current()', function() {
var successCallback, errorCallback;
beforeEach(function() {
successCallback = jasmine.createSpy('success');
errorCallback = jasmine.createSpy('error');
});
it("promise should resolve if Parse.User.current is truthy", function(){
spyOn(Parse.User, 'current').and.returnValue(true);
UserFactory.current().then(successCallback, errorCallback);
$rootScope.$digest();
expect(successCallback.calls.count()).toBe(1);
expect(errorCallback.calls.count()).toBe(0);
expect(Parse.User.current).toHaveCalledOnce();
});
it("promise should reject if Parse.User.current is falsy", function(){
spyOn(Parse.User, 'current').and.returnValue(false);
UserFactory.current().then(successCallback, errorCallback);
$rootScope.$digest();
expect(errorCallback.calls.count()).toBe(1);
expect(successCallback.calls.count()).toBe(0);
expect(Parse.User.current).toHaveCalledOnce();
});
});
});

Related

Angular unit test factory that uses http request

I am working on an angular js app with karma/Jasmine testing framework, I need to test a factory that returns a http promise but it always return undefined
here is my factory
angular.module('GithubUsers').factory('Users',['$http','$q',function($http,$q){
return{
getAllUsers:function(){
var defered= $q.defer();
$http({
url:'https://api.github.com/users',
method:'GET'
}).then(function(users){
defered.resolve(users.data);
},function(err){
defered.reject(err);
})
return defered.promise;
}
}
}])
here is my tests
Update thanks to your answers I modified my code to the following but no I got this error
Possibly unhandled rejection: {"status":0,"config":{"method":"GET","transformRequest":[null],"transformResponse":[null],"jsonpCallbackParam":"callback","url":"https://api.github.com/users?since=1","headers":{"Accept":"application/json, text/plain, /"},"cached":false},"statusText":""} thrown
describe('Test Users Factory',function(){
var $controller,
Users,
$rootScope,
$httpBackend,
$q;
beforeEach(module('GithubUsers'));
beforeEach(inject(function(_$controller_,_Users_,_$rootScope_,_$httpBackend_,_$q_){
$controller = _$controller_;
Users = _Users_;
$rootScope= _$rootScope_;
$httpBackend=_$httpBackend_;
}))
it('should get users',function(){
var result;
$httpBackend.whenGET('https://api.github.com/users?since=1').respond(function(){
return {data:[{id:2}],status:200};
})
Users.getAllUsers().then(function(res){
result = res;
});
$httpBackend.flush();
$rootScope.$digest()
expect(result).toBeTruthy();
})
})
Thanks in advance!
I think you need to pass a function that returns a array with 3 items in it, to whenGET().respond().
Maybe, you can try something like this:
beforeEach(angular.mock.inject(function (User, $httpBackend, $http) {
...
this.withOKUsers = function() {
var i1 = new User();
i1.id = 10;
return [200, JSON.stringify([ i1]), {}];
} ...
}));
...
it('should get users',function(){
$httpBackend
.whenGET('https://api.github.com/users')
.respond(this.withOKUsers);
Users.getAllUsers().then(function(res){
result = res;
});
$httpBackend.flush();
expect(result).not.toBeNull();
...
(I prefer to arrange spec outside of it() clause for better readability)
You're missing a $httpBackend.flush(); call after your test method call. It will invoke a success/error or then part and resolve a $q's promise properly. For more tests I would move a $httpBackend.whenGET to each test case separately so I can later verify it per use case but it's just my personal opinion.
I find it a little suspicious that you mix a $controller and a factory in one test. I would suggest to split them, and in controller test just check the calls to service methods and in a facotry test itself do a $httpBackend stuff.
Below I paste your test with my corrections. It works now for me:
describe('Test Users Factory', function () {
var Users,
$rootScope,
$httpBackend,
$q;
beforeEach(module('app.utils'));
beforeEach(inject(function (_Users_, _$rootScope_, _$httpBackend_, _$q_) {
Users = _Users_;
$rootScope = _$rootScope_;
$httpBackend = _$httpBackend_;
}));
afterEach(function () {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('should get users', function () {
var result;
$httpBackend.when('GET', "https://api.github.com/users").respond({ data: [{ id: 2 }], status: 200 });
Users.getAllUsers().then(function (res) {
result = res;
expect(result).toBeTruthy();
});
$httpBackend.flush();
$rootScope.$digest();
});
Important notices:
1)afterEach - check if no pending requests remain after your call
2) your url differ with a parameter ?since=1. But you do not give it as a parameter in your code so i do not understand why you added this parameter.
Maybe consider string concatenation with url and parameter ?

error in angular.mock.inject when testing service

I want to make a test of a service in AngularJS but there is always a problem with the injector.
Here is the service:
var projectApp = angular.module("projectApp", []);
projectApp.service("workTimeService", function(){
this.workHours = function(workHours){
return workHours;
}
}
Here is the test:
describe("Unit: Testing services", function(){
var workTimeService;
beforeEach(function(){
module("projectApp");
});
beforeEach(inject(function(_workTimeService_){
workTimeService = _workTimeService_;
}));
it('Should have funtion', function(){
expect(angular.isFunction(workTimeService.workHours)).toBe(true);
});
});
I have made it just as in the tutorials but jasmine says that workTimeService is undefined.

How to test saving a resource in a controller with a promise

I have a controller that saves a resource. I can't tell how to "access" the part of the code that executes after the promise resolves. What do I need to change about my test or controller in order to get it to work? Here's the code.
Controller:
'use strict';
/**
* #ngdoc function
* #name lunchHubApp.controller:AnnouncementsCtrl
* #description
* # AnnouncementsCtrl
* Controller of the lunchHubApp
*/
angular.module('lunchHubApp')
.controller('AnnouncementsCtrl', ['$scope', 'Announcement', function ($scope, Announcement) {
$scope.announcements = [];
$scope.save = function() {
// This next line is the part I'm finding hard to test.
new Announcement($scope.announcement).create().then(function(announcement) {
$scope.foo = 'bar'
});
};
}]);
Test:
'use strict';
describe('AnnouncementsCtrl', function() {
beforeEach(function() {
module('lunchHubApp', 'ng-token-auth')
});
it('sets scope.announcements to an empty array', inject(function($controller, $rootScope) {
var scope = $rootScope.$new(),
ctrl = $controller('AnnouncementsCtrl', { $scope: scope });
expect(scope.announcements).toEqual([]);
}));
describe('save', function() {
it('works', inject(function($controller, $rootScope, _$httpBackend_) {
var $httpBackend = _$httpBackend_;
var scope = $rootScope.$new(),
ctrl = $controller('AnnouncementsCtrl', { $scope: scope });
expect(scope.announcements.length).toBe(0);
var announcement = {
restaurantName: 'Bangkok Taste',
userId: 1
};
scope.announcement = announcement;
$httpBackend.expect('POST', '/api/announcements').respond(200, announcement);
scope.save();
scope.$digest();
expect(scope.foo).toEqual('bar');
}));
});
});
Update: here's the way I ended up modifying my controller test. The following passes and has been refactored from the original.
'use strict';
describe('AnnouncementsCtrl', function() {
var $httpBackend,
announcement,
scope,
ctrl;
beforeEach(function() {
module('lunchHubApp');
inject(function($injector) {
$httpBackend = $injector.get('$httpBackend');
scope = $injector.get('$rootScope').$new();
ctrl = $injector.get('$controller')('AnnouncementsCtrl', { $scope: scope });
announcement = { restaurantName: 'Bangkok Taste' };
scope.announcement = { restaurantName: 'Jason\'s Pizza' };
$httpBackend.expect('GET', '/api/announcements').respond([announcement]);
});
});
it('sets scope.announcements to an empty array', function() {
expect(scope.announcements).toEqual([]);
});
it('grabs a list of announcements', function() {
expect(scope.announcements.length).toBe(0);
$httpBackend.flush();
expect(scope.announcements.length).toBe(1);
});
describe('save', function() {
beforeEach(function() {
$httpBackend.expect('POST', '/api/announcements').respond(200, { restaurantName: 'Foo' });
scope.save();
$httpBackend.flush();
});
it('adds an announcement', function() {
expect(scope.announcements.length).toBe(2);
});
it('clears the restaurant name', function() {
expect(scope.announcement.restaurantName).toEqual('');
});
});
});
I think what you're doing is good. Since the Angular resources are factories using the $http service in a restful way, you should use the expect of the $httpBackend just as you did.
One thing that you miss however is that you need to make sure your promise is resolved. But write async tests can be tricky in some cases. To do so, you have to use the flush() method of $httpBackend to force your test to be synchronous.
After the flush, you can make your expect normally. Also you might have to move your expectPOST before your $rootScope.$new() statement.
You can go with a change like this, I don't think the $digest() is necessary:
$httpBackend.expect('POST', '/api/announcements').respond(200, announcement);
scope.save();
$httpBackend.flush();
expect(scope.foo).toEqual('bar');
The tests you've started writing seem to be testing not just AnnouncementsCtrl, but the Announcements service/factory as well. The signs of this in this case are
You're not mocking the Announcements service/factory / not stubbing any of its methods.
There is no code in the AnnouncementsCtrl regarding making http requests, and yet you're using $httpBackend.expect(... in the tests for them.
The success/failure of the tests that claim to test AnnouncementsCtrl will succeed or fail depending on code in the Announcements service/factory.
This goes against what unit tests are usually used for: testing each component in isolation. Keeping the focus of this answer on testing the success callback passed to the then method of the promise returned by create, my suggestion is to mock the Announcements service/factory, so its create method returns a promise that you can control in the test. This mock would be of the form:
var MockAnnouncement = null;
var deferred = null;
beforeEach(module(function($provide) {
MockAnnouncement = function MockAnnouncement() {
this.create = function() {
return deferred.promise;
};
};
$provide.value('Announcement', MockAnnouncement);
}));
You would then have to make sure that you create deferred object before each test:
beforeEach(inject(function($rootScope, $controller, $q) {
$scope = $rootScope.$new();
deferred = $q.defer(); // Used in MockAnnouncement
ctrl = $controller('AnnouncementsCtrl', {
$scope: $scope
});
}));
This deferred object is then resolved in the test:
it('calls create and on success sets $scope.foo="bar"', function() {
$scope.save();
deferred.resolve();
$scope.$apply();
expect($scope.foo).toBe('bar');
});
A slightly extended version of this, testing a few other behaviours of the controller as well, can be seen at http://plnkr.co/edit/v1bCfmSPmmjBoq3pfDsk

Testing promise value returned by service

How can I test the value of a promise, returned by a service? In the $q documentation, the promise value is preset in the test using resolve(value). Other approaches test the service logic in a controller, using the fact that AngularJS evaluates the promises and binds the values the $scope.
In my opinion, none of these approaches actually test the logic of the service in the place where it should be tested. How can I test that the resolved promise (which is returned by the service) contains the correct value?
Here an example:
myApp.service('service', function($q){
var obj = {};
obj.test = function() {
var deferred = $q.defer();
deferred.resolve(false);
return deferred.promise;
}
return obj;
});
In order to test the service, I want to do the following in theory (which does not work in practice):
var $q, service;
beforeEach(function () {
module('myModule');
service = $injector.get('service');
});
describe('...', function() {
it('Testing whether promise contains correct value', function() {
var myPromise = service.test();
myPromise.then(function(value) {
expect(value).toBeFalsy();
});
});
});
I believe you are injecting the service in a wrong way. You have to use the underscore notation. Please refer this link. http://nathanleclaire.com/blog/2014/04/12/unit-testing-services-in-angularjs-for-fun-and-for-profit/
So, your test should look something like this.
var service;
beforeEach(function () {
module('myModule');
inject(function(_service_) {
service = _service_;
});
});
describe('...', function() {
it('Testing whether promise contains correct value', function() {
var myPromise = service.test();
myPromise.then(function(value) {
expect(value).toBeFalsy();
});
});
});

angular js force.com Javascript remoting

I am new to Angular JS and really struggling to get data into my controller from javascript remoting. Has anyone done this before?
Visualforce.remoting.Manager.invokeAction('{!$RemoteAction.Forecasting.Users}',
new Object(),function(result, event){
return result
}
);
How on earth do i get the data into the controller :
var forecastingCtrl = function($scope, $q){
$scope.lengths = function(){return '5'};
$scope.users = [];
}
EDIT : Following works fine:
fc.service('Users', function($rootScope, $q) {
this.get = function(){
var deferred = $q.defer();
Visualforce.remoting.Manager.invokeAction('{!$RemoteAction.Forecasting.Users}',new Object(),function(result, event){
if(event) {
$rootScope.$apply(function() {
deferred.resolve(result);
});
} else {
deferred.reject(result);
}
});
return deferred.promise;
}
});
function forecastingCtrl($scope, $q, Users){
$scope.lengths = function(){return '5'};
var promise = Users.get();
promise.then(
function(users){
$scope.users = users;
},
function(reason){
alert('Failed: ' + reason);
}
);
};
I haven't used javascript remoting (sounds interesting) but what you will probably want to do is to encapsulate your visualforce code inside a service, having a method (most likely a promise) to return your data and inject the service into your controller. The upside of this is that you can unit test your controller by injecting a mock or spy of your data service. The pattern would be very much like how you would use angular's $http and $resource services. Hopefully this gets you on the right track.

Resources