SinSync SinSync - 1 month ago 12
AngularJS Question

Unit test an angular controller and service which uses a promise?

I cannot get the test result to pass I'm using a very basic implementation to understand testing deeper.

I have a factory which returns a promise, accessed from my controller. I want to test that the call succeeds and assigns the response to the

repos
var. Following is the code:

'use strict';

angular.module('app')
.factory('searchServ', function ($timeout, $q, $http) {
return {
fetch: function(user) {
var deferred = $q.defer();

$timeout(function(){
$http({method: 'GET', url: 'https://api.github.com/users/' + user + '/repos'}).then(function(repos) {
deferred.resolve(repos.data);
}, function(reason){
deferred.reject(reason.status);
console.log(reason);
});
}, 30);

return deferred.promise;
}
};
})
.controller('MainCtrl', function ($scope, searchServ) {
$scope.results = function(user) {
$scope.message = '';
searchServ.fetch(user).then(function (repos) {
if(repos.length){
$scope.message = '';
$scope.repos = repos;
}
else{
$scope.message = 'not found'
}
}, function (){
$scope.message = 'not found';
});
};
});

//Test

'use strict';

describe('MainCtrl', function () {
var scope, searchServ, controller, deferred, repos = [{name: 'test'}];
// load the controller's module
beforeEach(module('app'));

beforeEach(inject(function($controller, $rootScope, $q) {
searchServ = {
fetch: function () {
deferred = $q.defer();
return deferred.promise;
}
};
spyOn(searchServ, 'fetch').andCallThrough();
scope = $rootScope.$new();
controller = $controller('MainCtrl', {
$scope: scope,
fetchGithub: fetchGithub
});


}));
it('should test', function () {
expect(scope.test).toEqual('ha');
});

it('should bind to scope', function () {
scope.results();
scope.$digest();
expect(scope.message).toEqual('');
//expect(scope.repos).not.toBe(undefined);
});
});


Running the test gives me the following error :


TypeError: undefined is not a function (evaluating 'spyOn(searchServ, 'fetch').andCallThrough()') in test/spec/controllers/main.js (line 15)



Any idea how I can test this such that it tests the scope binding as well as the async call?

Answer

There are a lot of issues with your code.

I've created this Plunkr for the purpose. index.js is the file with your code and test cases. I've edited most of the part according to the conventions and best-practices.

There are a few pointers I wanted to give you:

  • Since $http returns a promise, you should use that, instead of resolving the promise and creating another promise from your method. Not sure why is timeout used. So I removed $q and $timeout from searchServ's dependencies.
  • I did the same in the test case by removing the deferred variable that you used.
  • You should be using angular-mocks.js to mock your services and other dependencies instead of defining a service inside your test case(The way you have did.)
  • You should create separate describe blocks for testing different parts of your code(a controller in this case).

Hope this helps!

Comments