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

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?

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!

Related

How to write test case for JSON getting form factory in AngularJS

I am trying to write the test cass for the factory which is returing a JSON response.
But I am getting the error:
Error: [$injector:unpr] http://errors.angularjs.org/1.4.1/$injector/unpr?p0=serviceProvider%20%3C-%20service
at Error (native)
Here is my code:
(function () {
angular.module('uspDeviceService',[]).factory('getDevice', GetDevice);
GetDevice.$inject = ['$http'];
function GetDevice($http) {
getDeviceList = function() {
return $http.get("static/test-json/devices/device-list.json");
}
return {
getDeviceList: getDeviceList
}
}
}());
Code for Test case:
describe('Get Product test', function() {
beforeEach(module('uspDeviceService'));
var service, httpBackend, getDevice ;
beforeEach(function () {
angular.mock.inject(function ($injector) {
//Injecting $http dependencies
httpBackend = $injector.get('$httpBackend');
service = $injector.get('service');
getDevice = $injector.get('getDevice');
})
});
console.log('Injection Dependencies is done');
describe('get Device List', function () {
it("should return a list of devices", inject(function () {
httpBackend.expectGET("static/test-json/devices/device-list.json").respond("Response found!");
httpBackend.flush();
}))
})
});
I am new to Angular Unit testing, can anyone please help me, where I am going wrong..
Two things that jump out at me:
Your angular.module declaration is defining a module, not getting the module. I would encourage you to split that up so that it's a fair bit more clear what your intent is.
angular.module('uspDeviceService', []);
angular.module('uspDeviceService').factory('getDevice', GetDevice);
It likely works as-is, but clarity is important.
What is...service? It's not defined anywhere in your code, and Angular can't find it either, hence the error message. You may be looking to get getDevice instead. Also, name your test variable with respect to what it actually is, so you don't confuse yourself.
// defined above
var getDevice;
// while injecting
getDevice = $injector.get('getDevice');
Supposing that you have an angularjs controller myController defined in myModule. The controller do some action when the api call is success and shows a flash message when api returns success = false. The your controller code would be something like
angular.module('myModule')
.controller( 'myController', function ( $scope,flashService, Api ) {
Api.get_list().$promise.then(function(data){
if(data.success) {
$scope.data = data.response
}
else{
flashService.createFlash(data.message, "danger");
}
});
});
Now to test both success = true and success = false we
describe('myController', function(){
var $rootScope, $httpBackend, controller, flashService;
var apilink = 'http://apilink';
beforeEach(module('myModule'));
beforeEach(inject(function(_$httpBackend_,_$rootScope_, _$controller_, _flashService_) {
$rootScope = _$rootScope_;
$httpBackend = _$httpBackend_;
flashService = _flashService_;
controller = _$controller_("myController", {$scope: $rootScope});
}));
it('init $scope.data when success = true', function(){
$httpBackend.whenGET(apilink)
.respond(
{
success: true,
response: {}
});
$httpBackend.flush();
expect($rootScope.data).toBeDefined();
});
it('show flash when api request failure', function(){
spyOn(flashService, 'createFlash');
$httpBackend.whenGET(apilink)
.respond(
{
success: false
});
$httpBackend.flush();
expect(flashService.createFlash).toHaveBeenCalled();
});
});
You are always going to mock the response because here we are testing the javascript code behaviour and we are not concerned with the Api. You can see when success the data is initialized and when success is false createFlash is called.
As far as test for factory is concerned you can do
describe('Get Product test', function() {
beforeEach(module('uspDeviceService'));
var service, httpBackend, getDevice ;
beforeEach(function () {
inject(function ($injector) {
httpBackend = $injector.get('$httpBackend');
service = $injector.get('service');
getDevice = $injector.get('getDevice');
});
});
describe('get Device List', function () {
it("should return a list of devices", inject(function () {
httpBackend.expectGET("static/test-json/devices/device- list.json").respond("Response found!");
var result = getDevice.getDeviceList();
httpBackend.flush();
expect(result).toEqual('Response found!');
}));
});
});

angular directive with dependencies testing

I am trying to be a good developer & write some tests to cover a directive I have. The directive has a service injected in which makes a call to a webApi endpoint.
When I run the test (which at minute expects 1 to equal 2 so I can prove test is actually running!!) I get an error that an unexpected request GET has been made to my real endpoint even though I thought I had mocked/stubbed out the service so test would execute. My test looks something like the below:
I thought that by calling $provide.service with the name of my service and then mocking the method "getUserHoldings" then this would automatically be injected at test time, have I missed a trick here? The path of the endpoint the unexpected request is contained in the actual getUserHoldings method on the concrete service.
Thanks for any help offered as driving me potty!!!
describe('directive: spPlanResults', function () {
var scope;
var directiveBeingTested = '<sp-plan-results></sp-plan-results>';
var element;
beforeEach (module('pages.plans'));
beforeEach (inject(function ($rootScope,
$compile,
currencyFormatService,
_,
moment,
plansModel,
appConfig,
$timeout,
$q,
$provide) {
scope = $rootScope.$new();
$provide.service('plansService', function () {
return {
getUserHoldings: function() {
var deferred = $q.defer();
return deferred.resolve([
{
class: 'Class1',
classId: 2,
award: 'Award1',
awardId : 2
}]);
}
};
});
element = $compile(directiveBeingTested)(scope);
scope.$digest();
});
it ('should be there', inject(function() {
expect(1).equals(2);
}));
});
Referencing - http://www.mikeobrien.net/blog/overriding-dependencies-in-angular-tests/ - it would work if you did your '$provide' configuration in the 'module's context i.e. do something like -
describe('directive: spPlanResults', function () {
var scope;
var directiveBeingTested = '<sp-plan-results></sp-plan-results>';
var element;
beforeEach(module('pages.plans', function($provide) {
$provide.value('plansService', function() {
return {
getUserHoldings: function() {
var deferred = $q.defer();
return deferred.resolve([{
class: 'Class1',
classId: 2,
award: 'Award1',
awardId: 2
}]);
}
};
});
}));
beforeEach(inject(function($rootScope, $compile, currencyFormatService, _, moment, plansModel, appConfig, $timeout, $q) {
scope = $rootScope.$new();
element = $compile(directiveBeingTested)(scope);
scope.$digest();
});
it('should be there', inject(function() {
expect(1).equals(2);
})); });

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

Using Jasmine spyOn with $resource

I’m trying to use Jasmine spies for testing a controller that calls query on a $resource. I can get a successful test when I write my call to the resource as follows (implementation 1 in the plunk linked below)
function($scope, bagelApiService) {
bagelApiService
.query()
.$promise
.then(function(bagelsResponse) {
$scope.bagels = bagelsResponse;
$scope.somethingAfterBagelsLoad = true;
});
}
But I would rather call the resource like this (implementation 2 in the plunk linked below)
function($scope, bagelApiService) {
bagelApiService.query(function(bagelsResponse) {
$scope.bagels = bagelsResponse;
$scope.somethingAfterBagelsLoad = true;
});
}
Here is my spec
describe('BreakfastCtrl', function() {
var $q,
$rootScope,
_scope,
mockBagelsResponse = [{name: 'foobagel'}, {name: 'barbagel'}];
beforeEach(module('BreakfastApp'));
beforeEach(inject(function($controller, $q, $rootScope, bagelApiService) {
_scope = $rootScope.$new();
var queryDeferred = $q.defer();
spyOn(bagelApiService, 'query').andReturn({$promise: queryDeferred.promise});
$controller('BreakfastCtrl', {
'$scope': _scope,
'bagelApiService': bagelApiService
});
queryDeferred.resolve(mockBagelsResponse);
$rootScope.$apply();
}));
it('should set scope.bagels', function() {
expect(_scope.bagels).toEqual(mockBagelsResponse);
});
});
Any idea why implementation 2 fails the test (even though it runs fine), and how the test can be written to pass with implementation 2?
click here for plunk
It's because you don't mimic the query() completely.
It should be like this (at least to make both of your implementations work).
spyOn(bagelApiService, 'query').andCallFake(function (callback) {
queryDeferred.promise.then(callback);
return { $promise: queryDeferred.promise };
});
Example Plunker: http://plnkr.co/edit/wGAytf5ASSJwut4WUwGO?p=preview

Jasmine unit test asynchronous controller method

I'm using Jasmine to unit test an Angular controller which has a method that runs asynchronously. I was able to successfully inject dependencies into the controller but I had to change up my approach to deal with the async because my test would run before the data was loaded. I'm currently trying to spy on the mock dependency and use andCallThrough() but it's causing the error TypeError: undefined is not a function.
Here's my controller...
myApp.controller('myController', function($scope, users) {
$scope.user = {};
users.current.get().then(function(user) {
$scope.user = user;
});
});
and my test.js...
describe('myController', function () {
var scope, createController, mockUsers, deferred;
beforeEach(module("myApp"));
beforeEach(inject(function ($rootScope, $controller, $q) {
mockUsers = {
current: {
get: function () {
deferred = $q.defer();
return deferred.promise;
}
}
};
spyOn(mockUsers.current, 'get').andCallThrough();
scope = $rootScope.$new();
createController = function () {
return $controller('myController', {
$scope: scope,
users: mockUsers
});
};
}));
it('should work', function () {
var ctrl = createController();
deferred.resolve('me');
scope.$digest();
expect(mockUsers.current.get).toHaveBeenCalled();
expect(scope.user).toBe('me');
});
});
If there is a better approach to this type of testing please let me know, thank you.
Try
spyOn(mockUsers.current, 'get').and.callThrough();
Depends on the version you have used: on newer versions andCallThroungh() is inside the object and.
Here the documentation http://jasmine.github.io/2.0/introduction.html

Resources