Angular - Unit Testing Modal Factory Call - angularjs

I'm trying to verify that a factory function is called from within a modal function, if passed a valid file object. The factory is injected into the modal controller and when the test is run the factory is undefined. What is the proper way to test that a factory function is called from within a modal?
modal controller
angular.module('vicModule')
.controller('vicModalController', vicModalController);
vicModalController.$inject = [
'$uibModalInstance',
'$uibModal',
'utilFunctionsFactory'
]
function vicModalController($uibModalInstance, $uibModal, utilFunctionsFactory) {
mvm.uploadVICs = uploadVICs;
function uploadVICs(file, error) {
if (file == null) {
data.errorMessage = 'file is not found or not supported';
return;
}
if(error.length > 0) {
$uibModalInstance.close();
data.errorMessage = 'reading file error';
return;
} else {
var fileData = utilFunctionsFactory.validateCSV(file, error);
}
}
}
modal controller test
describe('vicModalController', function() {
var $controller, vicModalController, vicFactory, utilFunctionsFactory, $q, $scope, $httpBackend, deferred, $uibModalInstance;
beforeEach(module('fotaAdminPortal'));
beforeEach(module('vicModule'));
beforeEach(inject(function(_$controller_, _vicFactory_, _utilFunctionsFactory_, _$q_, _$rootScope_, _$httpBackend_) {
$controller = _$controller_;
vicFactory = _vicFactory_;
utilFunctionsFactory: _utilFunctionsFactory_;
$q = _$q_;
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
$uibModalInstance = { // Create a mock object using spies
close: jasmine.createSpy('modalInstance.close'),
dismiss: jasmine.createSpy('modalInstance.dismiss'),
result: {
then: jasmine.createSpy('modalInstance.result.then')
}
};
vicModalController = $controller('vicModalController', {
vicFactory: vicFactory,
utilFunctionsFactory: utilFunctionsFactory,
$uibModalInstance: $uibModalInstance
});
}));
it('should be defined', function() {
expect(vicModalController).toBeDefined();
});
describe('uploadVICS()', function() {
beforeEach(inject(function(_utilFunctionsFactory_) {
utilFunctionsFactory = _utilFunctionsFactory_;
spyOn(utilFunctionsFactory, 'validateCSV').and.callFake(function() {
return {};
});
}));
it('should call validateCSV() with valid file', function() {
vicModalController.uploadVICs({}, []);
expect(utilFunctionsFactory.validateCSV()).toHaveBeenCalledWith({}, []);
});
});
});
edit
I had the assignment wrong in the beforeEach:
utilFunctionsFactory: utilFunctionsFactory; //incorrect colon
utilFunctionsFactory = utilFunctionsFactory; // should be assigned

A couple of issues I see:
You are doing:
utilFunctionsFactory: _utilFunctionsFactory_;
instead of:
utilFunctionsFactory = _utilFunctionsFactory_;
Also you are asserting that the result of validateCSV is called:
expect(utilFunctionsFactory.validateCSV()).toHaveBeenCalledWith({}, []);
Instead of validateCSV itself:
expect(utilFunctionsFactory.validateCSV).toHaveBeenCalledWith({}, []);

Related

Unit test $mdDialog angular material

I called one $mdDialog inside a function. I want to unit-test $mdDialog ok and cancel cases.
The below is my controller code (app.controller.js).
(function () {
'use strict';
app.controller('AppCtrl', AppCtrl);
AppCtrl.$inject = ['$scope', '$mdDialog'];
function AppCtrl($scope, $mdDialog) {
$scope.saveEntry = function (ev) {
var confirm = $mdDialog.prompt()
.title('Save Entry')
.textContent('If you want, you can add a description to explain what you changed.')
.placeholder('Version Description')
.ariaLabel('Version Description')
.initialValue('')
.targetEvent(ev)
.ok('Save')
.cancel('Cancel');
$mdDialog.show(confirm).then(function (result) {
$scope.status = true;
}, function () {
$scope.status = false;
});
};
}
})();
The following is the spec code (app.controller.spec.js) :
describe('Unit test AppController: mdDialog', function () {
var $controller, $mdDialog;
beforeEach(function () {
module('App');
inject(function (_$controller_, _$mdDialog_) {
$controller = _$controller_;
$mdDialog = _$mdDialog_;
});
});
it(': Opened', function () {
var $scope = {};
var controller = $controller('AppCtrl', { $scope: $scope });
var $mdDialogOpened = false;
$mdDialog.show = jasmine.createSpy().and.callFake(function () {
$mdDialogOpened = true;
});
$scope.saveEntry();
$scope.$digest();
expect($mdDialog.show).toHaveBeenCalled;
expect($mdDialogOpened).toBe.true;
});
});
when I running the above code I'm getting the following error:
TypeError: Cannot read property 'then' of undefined
I referred this GitHub issue https://github.com/angular/material/issues/1482. But I'm not getting solution for my problem
Thanks in advance
The problem is that you are injecting one version of $mdDialog, and trying to test on another one.
You could try something like this:
describe('Unit test AppController: mdDialog', function () {
var ctrl, mdDialog, scope;
beforeEach(function () {
module('App');
inject(function ($rootScope, $controller, $mdDialog) {
scope = $rootScope.$new();
mdDialog = $mdDialog; //keep the reference, for later testing.
spyOn(mdDialog, 'show');
mdDialog.show.and.callFake(function () {
return {
then: function (callBack) {
callBack(true); //return the value to be assigned.
}
}
});
ctrl = $controller('AppCtrl',{$scope:scope, $mdDialog:mdDialog}); //Inject the dependency
});
});
it(': Opened', function () {
scope.saveEntry(); //exercise the method.
scope.$digest();
expect(mdDialog.show).toHaveBeenCalled();
expect(scope.status).toBe(true);
});
});
Something very similar should work.
hope this help.

Method undefined in Angular JS service unit test

I am writing unit tests for my existing AngularJS app. There are just four methods in this service. I was able to getFollowUpList to work, but refresh() is not working and it is a very simple method.
The refresh method should simply set deferredGetFollowUpList = null and return true in my test.
There error I'm getting is: TypeError: Cannot read property 'then' of undefined, so my refresh method is undefined. Why is this the case? Thanks
Service
(function () {
"use strict";
angular
.module("all.patient.details")
.factory("followUpListService", ["$rootScope", "$http", "userContext", "$q", "$uibModal", "htmlBaseUrl", FollowUpListService]);
function FollowUpListService($rootScope, $http, userContext, $q, $uibModal, htmlBaseUrl) {
var deferredGetFollowUpList = null;
return {
getFollowUpList: getFollowUpList,
displayModal: displayModal,
refresh: refresh,
save: save
}
function refresh() {
deferredGetFollowUpList = null;
}
}
})();
Unit Test
describe("followUpListService", function () {
beforeEach(module("all.patient.details"));
var followUpListService = {};
var $httpBackend;
var htmlBaseUrlMock;
var returnedFollowUpListData;
var deferredGetFollowUpList;
var $rootScope;
var $q;
var $uibModal;
beforeEach(function () {
htmlBaseUrlMock = { format: function () { } };
module(function ($provide) {
$provide.value("htmlBaseUrl", htmlBaseUrlMock);
});
inject(function (_$rootScope_, _$httpBackend_, _$q_, _$uibModal_, _followUpListService_) {
$rootScope = _$rootScope_;
$httpBackend = _$httpBackend_;
$q = _$q_;
$uibModal = _$uibModal_;
followUpListService = _followUpListService_;
});
});
afterEach(function () {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it("calls refresh()", function () {
followUpListService.refresh()
.then(function (data) {
deferredGetFollowUpList = data;
});
expect(deferredGetFollowUpList).toBe(null);
});
As deferredGetFollowUpList is service level variable, Can you write your test as -
followUpListService.deferredGetFollowUpList = data; //Any Mock Data
followUpListService.refresh();
expect(followUpListService.deferredGetFollowUpList).toBe(null);

Unit Test large number of injected services using Jasmine

I'm very new to the AngularJs unit testing with Jasmine.So could you tell me how can I test below mentioned controller and countyService.getAllCountiesAsync() method using Jasmine.Thanks in advance.
Note : The controller below is having more than 50 injected services (I have shown few below).So I don't know which method is good for mock those also ?
Controller :
(function () {
appModule.controller('myController', [
'$scope', '$modalInstance', 'abp.services.app.property', 'abp.services.app.county', 'abp.services.app.propertyClass', 'abp.services.app.schoolDistrict'
function ($scope, $modalInstance, propertyService, countyService, propertyClassService, schoolDistrictService) {
vm.getAllCounties = function () {
countyService.getAllCountiesAsync().success(function (result) {
vm.counties = result.items;
});
};
vm.getAllCounties();
} ]);
})();
WebApi method :
public async Task<ListResultOutput<CountyListDto>> GetAllCountiesAsync()
{
var counties = await _countyRepository
.GetAllListAsync();
return new ListResultOutput<CountyListDto>(counties.OrderBy(o => o.Name).MapTo<List<CountyListDto>>());
}
You should write test cases for service and controller.
For services 'Daan van Hulst' has already given answer and for controller see below code:
describe('service tests', function () {
var $compile,$controller,myController, $rootScope, propertyService, countyService, propertyClassService, schoolDistrictService;
//All module dependencies
beforeEach(module('your-app-name'));
//inject required services and _$controller_ to create controller
beforeEach(inject(function(_$compile_,_$controller_, _$rootScope_, _propertyService_, _countyService_, _propertyClassService_, _schoolDistrictService_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
$controller = _$controller_; // This is IMP
countyService = _countyService_;
// remianig services
// Now create controller
myController = $controller('myController', {
$scope : scope,
propertyService : propertyService // all other services
});}
it('should test something', function() {
spyOn(countyService, 'getAllCountiesAsync').and.callFake(function () {
var d = q.defer();
d.resolve({ items: [{data:'somedata'}] });
return d.promise;
});
myController.getAllCounties();
expect(myController.counties).not.toBe(null);
});
Update
I might have made mistakes, but this is the idea:
describe('service tests', function () {
var $compile, $rootScope, scope, vm, propertyService, countyService, propertyClassService, schoolDistrictService;
beforeEach(module('your-app-name'));
beforeEach(inject(function(_$compile_, _$rootScope_, $controller, _propertyService_, _countyService_, _propertyClassService_, _schoolDistrictService_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
scope = $rootScope.$new();
propertyService = _propertyService_;
countyService = _countyService_;
propertyClassService = _propertyClassService_;
schoolDistrictService = _schoolDistrictService_;
vm = $controller('myController', {'$scope': scope})
spyOn(countyService, "getAllCountiesAsync").and.callFake(function() {
var deferred = $q.defer();
deferred.resolve({data: [{id:0}]});
return deferred.promise;
});
}));
it('can do remote call', inject(function() {
//Arrange
result = [{id:0}];
// Act
vm.getAllCounties();
// Assert
expect(vm.counties).toBe(result); //assert to whatever is resolved in the spyOn function
});
});
}
I assume that you create Angular services for all your services and that you app is working. Then, you can inject them in your tests:
describe('service tests', function () {
var $compile, $rootScope, propertyService, countyService, propertyClassService, schoolDistrictService;
beforeEach(module('your-app-name'));
beforeEach(inject(function(_$compile_, _$rootScope_, _propertyService_, _countyService_, _propertyClassService_, _schoolDistrictService_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
propertyService = _propertyService_;
countyService = _countyService_;
propertyClassService = _propertyClassService_;
schoolDistrictService = _schoolDistrictService_;
}));
it('should test something', function() {
expect(propertyService).toBeDefined();
expect(countyService).toBeDefined();
expect(propertyClassService).toBeDefined();
expect(schoolDistrictService).toBeDefined();
});
});
Update
I accidentally posted my solution in the answer above, so corrected it now. You can create your controller with $controller and pass in a scope object. You can also pass in any other dependencies. Then create a spy on the service, and once it gets called, call a different function which resolves a promise with mock data:
describe('service tests', function () {
var $compile, $rootScope, scope, vm, propertyService, countyService, propertyClassService, schoolDistrictService;
beforeEach(module('your-app-name'));
beforeEach(inject(function(_$compile_, _$rootScope_, $controller, _propertyService_, _countyService_, _propertyClassService_, _schoolDistrictService_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
scope = $rootScope.$new();
propertyService = _propertyService_;
countyService = _countyService_;
propertyClassService = _propertyClassService_;
schoolDistrictService = _schoolDistrictService_;
// Create the controller, and pass in the scope with possible variables that you want to mock.
vm = $controller('myController', {'$scope': scope})
//Create a spy on your getAllCountiesAsync function and make it return a mock promise with mock data.
spyOn(countyService, "getAllCountiesAsync").and.callFake(function() {
var deferred = $q.defer();
deferred.resolve({data: [{id:0}]});
return deferred.promise;
});
}));
it('can do remote call', inject(function() {
//Arrange
result = [{id:0}];
// Act
vm.getAllCounties();
//I think that you also have to do this, but I am not a 100% sure.
scope.$apply();
// Assert
expect(vm.counties).toBe(result); //assert to whatever is resolved in the spyOn function
});
});
}

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();

How can we test non-scope angular controller methods?

We have few methods in Angular Controller, which are not on the scope variable.
Does anyone know, how we can execute or call those methods inside Jasmine tests?
Here is the main code.
var testController = TestModule.controller('testController', function($scope, testService)
{
function handleSuccessOfAPI(data) {
if (angular.isObject(data))
{
$scope.testData = data;
}
}
function handleFailureOfAPI(status) {
console.log("handleFailureOfAPIexecuted :: status :: "+status);
}
// this is controller initialize function.
function init() {
$scope.testData = null;
// partial URL
$scope.strPartialTestURL = "partials/testView.html;
// send test http request
testService.getTestDataFromServer('testURI', handleSuccessOfAPI, handleFailureOfAPI);
}
init();
}
Now in my jasmine test, we are passing "handleSuccessOfAPI" and "handleFailureOfAPI" method, but these are undefined.
Here is jasmine test code.
describe('Unit Test :: Test Controller', function() {
var scope;
var testController;
var httpBackend;
var testService;
beforeEach( function() {
module('test-angular-angular');
inject(function($httpBackend, _testService_, $controller, $rootScope) {
httpBackend = $httpBackend;
testService= _testService_;
scope = $rootScope.$new();
testController= $controller('testController', { $scope: scope, testService: testService});
});
});
afterEach(function() {
httpBackend.verifyNoOutstandingExpectation();
httpBackend.verifyNoOutstandingRequest();
});
it('Test controller data', function (){
var URL = 'test server url';
// set up some data for the http call to return and test later.
var returnData = { excited: true };
// create expectation
httpBackend.expectGET(URL ).respond(200, returnData);
// make the call.
testService.getTestDataFromServer(URL , handleSuccessOfAPI, handleFailureOfAPI);
$scope.$apply(function() {
$scope.runTest();
});
// flush the backend to "execute" the request to do the expectedGET assertion.
httpBackend.flush();
// check the result.
// (after Angular 1.2.5: be sure to use `toEqual` and not `toBe`
// as the object will be a copy and not the same instance.)
expect(scope.testData ).not.toBe(null);
});
});
I know this is an old case but here is the solution I am using.
Use the 'this' of your controller
.controller('newController',['$scope',function($scope){
var $this = this;
$this.testMe = function(val){
$scope.myVal = parseInt(val)+1;
}
}]);
Here is the test:
describe('newDir', function(){
var svc,
$rootScope,
$scope,
$controller,
ctrl;
beforeEach(function () {
module('myMod');
});
beforeEach(function () {
inject(function ( _$controller_,_$rootScope_) {
$controller = _$controller_;
$rootScope = _$rootScope_;
$compile = _$compile_;
$scope = $rootScope.$new();
ctrl = $controller('newController', {'$rootScope': $rootScope, '$scope': $scope });
});
});
it('testMe inc number', function() {
ctrl.testMe(10)
expect($scope.myVal).toEqual(11);
});
});
Full Code Example
As is you won't have access to those functions. When you define a named JS function it's the same as if you were to say
var handleSuccessOfAPI = function(){};
In which case it would be pretty clear to see that the var is only in the scope within the block and there is no external reference to it from the wrapping controller.
Any function which could be called discretely (and therefore tested) will be available on the $scope of the controller.

Resources