Angular factory testing with Karma Jasmine. Says UnKnown provider - angularjs

This is my angular code
var app = angular.module('FleetTask', ['ui.router']);
app.constant('settings', {
ApiURL: 'http://localhost/FleetTask.API',
MsgDuration: 4000,
ProductURL: '/api/product',
ProductGroupURL: '/api/productgroup',
DataAreaURL: '/api/dataarea'
});
app.factory('ProductGroupFactory', ['$http', 'settings', function ($http, settings) {
return {
getProductGroupList: function () {
return $http.get(settings.ApiURL + settings.ProductGroupURL);
},
getProductGroupByID: function (id) {
return $http.get(settings.ApiURL + settings.ProductGroupURL + "/" + id);
},
saveProductGroup: function (productGroup) {
return $http.put(settings.ApiURL + settings.ProductGroupURL, productGroup);
},
deleteProductGroup: function (id) {
return $http.delete(settings.ApiURL + settings.ProductGroupURL + "/" + id);
},
getProductList: function () {
return $http.get(settings.ApiURL + settings.ProductURL);
},
getDataAreaList: function (lookup) {
return $http.get(settings.ApiURL + settings.DataAreaURL + "/dataarealist/" + lookup);
}
};
}]);
app.controller('ProductGroupController', function ($scope, ProductGroupFactory, $rootScope, settings) {
function init(pageIndex) {
$rootScope.pageTitle = "Product Group";
// Complete functionality
};
init();
});
This is my test spec:
describe('FleetTask', function () {
beforeEach(function () {
module('FleetTask');
module('ui.router');
});
describe('ProductGroupFactory', function () {
var scope, controller, productGroupFactory, settings;
beforeEach(inject(function ($injector, $rootScope, $controller, $provide) {
scope = $rootScope.$new();
settings = $injector.get('settings');
productGroupFactory = {
save: jasmine.createSpy()
};
$provide.value('ProductGroupFactory', productGroupFactory);
}));
it('Access Factory', function () {
expect(settings).toBeDefined();
});
});
});
This is the error I am getting
Error says UnKnown provider. Looks like I am not doing right way of injecting factory. 'settings' service got injected correctly, but not the factory. Help me to find out right process to inject factory. Thanks
Answer: Derived test spec I have made out of answers.
describe('FleetTask', function () {
var scope, controller, pgFactory, constants, httpBackend;
beforeEach(function () {
module('FleetTask');
module('ui.router');
module('angulartics');
module('angulartics.google.analytics');
inject(function (_settings_, $injector, $controller, _$rootScope_) {
$rootScope = _$rootScope_;
scope = $rootScope.$new();
constants = _settings_;
pgFactory = function () {
return $injector.get('ProductGroupFactory');
};
httpBackend = $injector.get('$httpBackend');
httpBackend.whenGET(constants.ApiURL + constants.ProductURL).respond(function (data) { scope.products = data; });
httpBackend.expectGET(constants.ApiURL + constants.ProductGroupURL);
httpBackend.expectGET(constants.ApiURL + constants.DataAreaURL + "/dataarealist/0");
controller = $controller;
controller('ProductGroupController', { '$scope': scope });
});
});
it('Check if settings service is defined', function () {
expect(constants).toBeDefined();
});
it('Check if ProductGroupFactory is defined', function () {
expect(pgFactory).toBeDefined();
});
it('Check if controller is defined', function () {
expect(controller).toBeDefined();
});
it('Access Controller', function () {
expect(scope.error).toBe('');
});
it('Check if controller is defined', function () {
var prds = scope.products;
var len = prds.length;
expect(len).toBe(0);
});
});

You need to access $provide as a part of module specification, it can't be injected. i.e
beforeEach(module("FleetTask", function ($provide) {
productGroupFactory = jasmine.createSpyObj('productGroupFactory', ['save']);
$provide.value('ProductGroupFactory', productGroupFactory);
}));
beforeEach(inject(function (_settings_, $rootScope, $controller) {
scope = $rootScope.$new();
settings = _settings_;
//...
}));
it('Access Factory', function () {
expect(settings).toBeDefined();
});

Related

Passing one of the parameters in constructor to unit test

I am trying to get/set "testModel.number" in my unit test but I can't seem to get it. When I run the test I get this error message:
Error: [$injector:unpr] Unknown provider: testModelProvider <- testModel
Here is the controller:
angular.module("TestApp", [])
.controller("IndexController", function ($scope, testModel) {
$scope.name = "test";
testModel = {
number: 0
}
if (testModel.number === 1) {
$scope.name = "test1";
} else {
$scope.name = "test2";
}
});
Here is the unit test:
describe('IndexController', function () {
var scope, createController;
beforeEach(module("TestApp"));
beforeEach(inject(function ($rootScope, $controller, testModel) {
scope = $rootScope.$new();
createController = function () {
return $controller('IndexController', {
'$scope': scope,
'testModel': testModel
})
}
}));
it('example test', function () {
var controller = createController();
testModel.number = 1;
expect(scope.name).toBe('test1');
});
});
I'm fairly new to unit testing so any suggestions would be great! Thanks in advance!
I think you need to pass the testModel object to the method creating the controller. Jasmine does not know how to inject custom providers.
describe('IndexController', function () {
var scope, createController;
beforeEach(module("TestApp"));
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
createController = function (testModel) {
return $controller('IndexController', {
'$scope': scope,
'testModel': testModel
})
}
}));
it('example test', function () {
var testModel = { number: 1 };
var controller = createController(testModel);
expect(scope.name).toBe('test1');
});
});
If you will have multiple tests that will need the testModel object, you can also define it at a global level as follows:
describe('IndexController', function () {
var scope, createController;
var testModel = { number: 1 };
beforeEach(module("TestApp"));
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
createController = function () {
return $controller('IndexController', {
'$scope': scope,
'testModel': testModel
})
}
}));
it('example test', function () {
var controller = createController();
expect(scope.name).toBe('test1');
});
});

Mocking $httpBackend on controller initialisation with unknown json data

When mocking using $httpBackend, How do I create the mock when I do not know exactly the type of data being returned. I want the http call to be on controller initialsation. One caveat is that it will be a json object being returned.
(function () {
'use strict';
angular
.module('app')
.service('dataService', dataService);
function dataService($http) {
this.getMovies = getMovies;
////////////////
function getMovies() {
return $http.get('./src/app/movies/data.json')
.then(function (response) {
return response.data
})
}
};
})();
;
(function () {
'use strict';
angular.module('app')
.controller('moviesController', moviesController);
moviesController.$inject = ['dataService'];
function moviesController(dataService) {
var vm = this
vm.movies;
vm.getMovies = getMovies;
getMovies();
function getMovies() {
return dataService.getMovies()
.then(function (data) {
return vm.movies = data.movies;
});
}
};
}());
;
describe('moviesController', function () {
var $controller,
moviesController,
dataService,
$httpBackend;
beforeEach(angular.mock.module('app'));
beforeEach(angular.mock.module('ui.router'));
beforeEach(inject(function (_$controller_, _dataService_, _$httpBackend_) {
$controller = _$controller_;
dataService = _dataService_;
$httpBackend = _$httpBackend_;
moviesController = $controller('moviesController', { dataService: dataService });
}))
it('should be defined', function () {
expect(moviesController).toBeDefined();
});
it('should initialise with a call to dataService.getMovies()', function () {
var url = "./src/app/movies/data.json";
var movies = {};
$httpBackend.expectGET(url).respond(200, movies);
moviesController.getMovies();
expect(moviesController.movies).toEqual(movies);
$httpBackend.flush();
});
});
;
Expected undefined to equal Object({ }).
You can set the return to be an object you define in the spec.
var response = {};
it('should initialise with a call to dataService.getMovies()', function () {
$httpBackend.expectGET("./src/app/movies/data.json").respond(response);
$httpBackend.flush();
});

Angular unit testing Controller with service

I'm trying to write a unit test using karma and jasmine for an Angular controller that depends on a service
storesController.js
(function () {
var app = angular.module('storesController', ['storesService']);
app.controller('StoresListController', function ($scope, StoresService) {
$scope.getStores = function () {
StoresService.getStores().then(function (data) {
$scope.stores = data.data;
});
};
$scope.getStores();
$scope.deleteStore = function (id) {
StoresService.deleteStore(id).then(function () {
$scope.getStores();
});
};
});
storesService.js
(function () {
var app = angular.module('storesService', []);
app.factory('StoresService', ['$http', function ($http) {
var stores = [];
stores.getStores = function () {
return $http.get(/api/getStores');
};
stores.deleteStore = function (storeID) {
return $http.delete(/api/deleteStore/'+storeID);
};
return stores;
}]);
})();
And the test,
controllers.spec.js
describe('StoresController', function () {
beforeEach(module('storesController'));
var scope;
var storesServiceMock;
var controller;
beforeEach(inject(function ($controller, $rootScope) {
storesServiceMock = {
getStores: function() {
},
deleteStores: function() {
}
};
spyOn(storesServiceMock, 'getStores').and.returnValue({name : 'TestName', country : 'TestCountry'})
scope = $rootScope.$new();
controller = $controller('StoresListController', {
$scope: scope, StoresService: storesServiceMock
});
}));
it('scope.stores should be defined', function () {
expect(scope.stores).toBeDefined;
});
});
And I'm getting
TypeError: StoresService.getStores(...).then is not a function at n.$scope.getStores
I've also tried width httpBackend but I'm not be able to make it work, any clue about what I'm doing wrong?
Have the spy return a promise.
With ES2015:
spyOn(storesServiceMock, 'getStores').and.returnValue(Promise.resolve({name : 'TestName', country : 'TestCountry'}));
With $q:
spyOn(storesServiceMock, 'getStores').and.callFake(function() {
var deferred = $q.defer();
deferred.resolve({name : 'TestName', country : 'TestCountry'}));
return deferred.promise;
});

How to restrict to invoke service dependencies while writing Unit test for Controller?

I have the following controller:
(function () {
"use strict";
angular.module('usp.configuration').controller('ConfigurationController', ConfigurationController);
ConfigurationController.$inject = ['$scope', '$rootScope', '$routeParams', 'configurationService'];
function ConfigurationController($scope, $rootScope, $routeParams, configurationService) {
//Get Master Gas List
configurationService.getMasterGasList().then(function (response) {
$scope.masterGasList = response.data.data;
});
$scope.convertToInt = function (str) {
if (!isNumberEmpty(str) && !isNaN(str)) {
return parseInt(str, 10);
}
return "";
}
$scope.convertToString = function (num) {
if (!isNumberEmpty(num) && !isNaN(num)) {
return num + "";
}
return "";
}
}
}());
And below is the test case for the controller:
describe("test suite for Configuration test controller", function() {
var scope = null;
var configurationService;
beforeEach(module("usp.configuration"));
beforeEach(inject(function($rootScope, $controller, _configurationService_) {
// Services
// _'s are automatically unwrapped
configurationService = _configurationService_;
// Controller Setup
scope = $rootScope.$new();
$controller("ConfigurationController", {
$scope: scope,
configurationService : configurationService
});
}));
it("should convert to int", function() {
expect(scope.convertToInt("2")).toEqual(2);
});
it("should return empty string", function() {
expect(scope.convertToInt("asd")).toEqual("");
});
});
I don't want to call that service while I am running the test case.
I am new to unit testing, I don't know how can I do this.
Please help me to do this?
You need to mock the dependencies with $provide
beforeEach(function () {
configurationServiceMock = {
getSomething: function () {
return 'mockReturnValue';
}
};
module(function ($provide) {
$provide.value('configurationService', configurationServiceMock);
});
});
see: Injecting a mock into an AngularJS service
Solution for your needs:
var configurationServiceMock = {
getMasterGasList: function () {
return {
then: function(callback) {}
};
}
};
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
controller = $controller('ConfigurationController', {
'$scope': scope,
'configurationService': configurationServiceMock
});
}));

Angular Unit Test Jasmine Spy error

The following controller is getting a TypeError: 'undefined' is not a function (evaluating sessionService.getCurrentPlace()). I have a Mock Service with that method being spied on. The other method on the mock service works fine. I've tried .AndReturns({..}) on the spy as well as .AndCallThrough() but no luck. Any idea what I'm missing, or am I going about this wrong? Much Thanks!
CONTROLLER:
'use strict';
angular.module('wallyApp')
.controller('addGatewayCtrl', function ($scope, $location, $filter, sessionService) {
/*
private members
*/
//set scope from session data
$scope.processSession = function (places) {
$scope.currentPlaceId = sessionService.getCurrentPlace();
if (!$scope.currentPlaceId) {
$scope.currentPlaceId = places[0].id;
}
$scope.place = $filter("getById")(places, $scope.currentPlaceId);
$scope.ready = true;
};
/*
setup our scope
*/
$scope.currentPlaceId = null;
$scope.place = {};
$scope.videoSrc = "/videos/gateway-poster.gif";
$scope.loaded = true;
/*
setup controller behaivors
*/
//set video or gif to show or hide video
$scope.setVideo = function () {
$scope.videoSrc = "/videos/gateway.gif";
};
$scope.setPoster = function () {
$scope.videoSrc = "/videos/gateway-poster.gif";
};
//initialize scope
$scope.setVideo();
//submit form
$scope.continue = function () {
$location.path("/setup/pair-gateway");
return false;
};
//cancel
$scope.back = function () {
$location.path("/setup/plan-locations");
return false;
};
//wifi
$scope.gotoWifi = function () {
$location.path("/setup/wifi");
return false;
};
/*
setup our services, etc
*/
//get our places from the cache
sessionService.get("places").then(function (places) {
if (!places || places.length < 1) {
sessionService.refreshPlaces(); //Note we don't care about the promise as our broadcast watch will pick up when ready
} else {
$scope.processSession(places);
}
}).catch(function (error) {
//TODO:SSW Call Alert Service??
});
//Watch broadcast for changes
$scope.$on("draco.placesRefreshed", function (event, data) {
sessionService.get("places").then(function (places) {
$scope.processSession(places);
});
});
});
UNIT TEST:
'use strict';
describe('addGatewayCtrl', function () {
var $q,
$rootScope,
$location,
$scope,
$filter,
mockSessionService,
completePath = "/setup/pair-gateway",
backPath = "/setup/plan-locations",
wifiPath = "/setup/wifi",
sessionDeferred,
sessionInitDeferred,
mockPlaces = [{ id: "0001" }];
beforeEach(module('wallyApp'));
beforeEach(inject(function (_$q_, _$rootScope_, _$location_, _$filter_) {
$q = _$q_;
$location = _$location_;
$rootScope = _$rootScope_;
$filter = _$filter_;
}));
beforeEach(inject(function ($controller) {
$scope = $rootScope.$new();
mockSessionService = {
get: function (contact) {
sessionDeferred = $q.defer();
return sessionDeferred.promise;
},
getCurrentPlace: function () {
return mockPlaces[0].id;
},
refreshPlaces: function () {
sessionInitDeferred = $q.defer();
return sessionInitDeferred.promise;
}
};
spyOn(mockSessionService, 'get').andCallThrough();
spyOn(mockSessionService, 'getCurrentPlace').andReturn(mockPlaces[0].id);
spyOn(mockSessionService, 'refreshPlaces').andCallThrough();
$controller('addGatewayCtrl', {
'$scope': $scope,
'$location': $location,
'$filter':$filter,
'sessionService': mockSessionService
});
}));
describe('call session service to get place data ', function () {
//resolve our mock place and session services
beforeEach(function () {
//resolve mocks
sessionDeferred.resolve(mockPlaces);
$rootScope.$apply();
});
//run tests
it('should have called sessionService get places', function () {
expect(mockSessionService.get).toHaveBeenCalledWith("places");
});
it('should have called sessionService get currentPlaceId', function () {
expect(mockSessionService.getCurrentPlace).toHaveBeenCalled();
});
it('should have set scope', function () {
expect($scope.place).toEqual(mockPlaces[0]);
});
});
});
So I figured it out. With nested deferred's you have to call $scope.$apply() in between. The following fixed it up (along with a few minor changes to the mock data responses, but those were trivial):
//resolve promises
activityMessagesDeferred.resolve(mockActivityMessages);
$rootScope.$apply();
$rootScope.$broadcast("draco.sessionRefreshed");
activityCountDeferred.resolve(mockActivityCount);
$rootScope.$apply();
placesDeferred.resolve(mockPlaces);
activityListDeferred.resolve(mockActivities);
$rootScope.$apply();

Resources