I am having trouble in my test for an ionic modal controller. The problem (or at least the problem I'm focusing on) is mocking up the $ionicModal.fromTemplateUrl function. According to ionic documentation, it's supposed to return a promise that resolves into an instance of the modal.
Here is my factory:
(function() {
'use strict';
angular.module('penta.app.main').factory('addEquipment', AddEquipment);
function AddEquipment($rootScope, $ionicModal) {
return {
openModal: function() {
var scope = $rootScope.$new(true);
scope.controller = new AddEquipmentController(scope, $ionicModal);
}
};
function AddEquipmentController(scope, $ionicModal) {
var controller = this;
$ionicModal.fromTemplateUrl('app/tracking/activityLog/addItems/equipment/addEquipment.html', {
scope: scope,
animation: 'slide-in-up'
}).then(function(modal) {
controller.modal = modal;
controller.openModal();
});
controller.openModal = function() {
controller.modal.show();
};
controller.closeModal = function() {
controller.modal.hide();
};
return controller;
}
}
})();
And here is my test:
(function() {
'use strict';
describe('AddEquipment', function() {
var controllerConstructor;
var addEquipment;
var mock;
var mockIonicModal;
var mockModal;
var scope;
var dfd;
beforeEach(module('penta.app.main'));
beforeEach(module('unitTest'));
beforeEach(module('app/tracking/activityLog/addItems/equipment/addEquipment.html'));
beforeEach(function() {
mockModal = sinon.stub({
show: function() {
},
hide: function() {
}
});
mockIonicModal = sinon.stub({
fromTemplateUrl: function() {
},
then: function() {
}
});
mockIonicModal.fromTemplateUrl.returns(mockModal);
});
beforeEach(function() {
module(function($provide) {
$provide.value('$ionicModal', mockIonicModal);
});
});
beforeEach(inject(function($rootScope, $controller, $q, ptiMock) {
controllerConstructor = $controller;
dfd = $q.defer();
scope = $rootScope.$new();
mock = ptiMock;
mockModal.$promise = dfd.promise;
}));
beforeEach(inject(function(_addEquipment_) {
addEquipment = _addEquipment_;
}));
it('exists', function() {
expect(addEquipment).to.exist;
});
describe('open', function() {
it.only('opens the modal', function() {
addEquipment.openModal();
dfd.resolve(mockModal);
scope.$digest();
expect(mockIonicModal.show.calledOnce).to.be.true;
});
});
function getController() {
return mockIonicModal.fromTemplateUrl.lastCall.args[0].scope.controller;
}
});
})();
I'm also unsure if my getController function will properly return the controller. This is my first time working with $ionicModal, so any pointers are appreciated. Thanks.
FIXED:
I didn't have the dfd set up properly. Also I had the show set as a function of mockIonicPopup when it is a function of mockModal.
(function() {
'use strict';
describe('AddEquipment', function() {
var controllerConstructor;
var addEquipment;
var mock;
var mockIonicModal;
var mockModal;
var scope;
var dfd;
beforeEach(module('penta.app.main'));
beforeEach(module('unitTest'));
beforeEach(module('app/tracking/activityLog/addItems/equipment/addEquipment.html'));
beforeEach(function() {
mockModal = sinon.stub({
show: function() {
},
hide: function() {
}
});
mockIonicModal = sinon.stub({
fromTemplateUrl: function() {
},
then: function() {
}
});
});
beforeEach(function() {
module(function($provide) {
$provide.value('$ionicModal', mockIonicModal);
});
});
beforeEach(inject(function($rootScope, $controller, $q, ptiMock) {
controllerConstructor = $controller;
dfd = $q.defer();
scope = $rootScope.$new();
mock = ptiMock;
mockIonicModal.fromTemplateUrl.returns(dfd.promise);
}));
beforeEach(inject(function(_addEquipment_) {
addEquipment = _addEquipment_;
}));
it('exists', function() {
expect(addEquipment).to.exist;
});
describe('openModal', function() {
it.only('opens the modal', function() {
addEquipment.openModal();
dfd.resolve(mockModal);
scope.$digest();
expect(mockModal.show.calledOnce).to.be.true;
});
});
function getController() {
return mockIonicModal.fromTemplateUrl.lastCall.args[0].scope.controller;
}
});
})();
Related
I have simple factory in AngularJS:
function usersListDataProviderFactory(UserResource, $q) {
var usersListDataProvider = {},
queryParams = {};
init(queryParams);
return {
initDataProvider: function (queryParams) {
init(queryParams);
},
getDataProviderPromise: function () {
return usersListDataProvider;
}
};
function init(queryParams) {
var defer = $q.defer();
UserResource.getUsers(queryParams).then(function (response) {
defer.resolve(response);
}, function (error) {
defer.reject(error);
});
usersListDataProvider = defer.promise;
}
}
I have written tests in Karma / Jasmine that pass after commenting the lines:
init(queryParams);
When I restore an automatic function call I get a message:
TypeError: undefined is not a function (evaluating 'UserResource.getUsers(queryParams).then')
I know the problem is the configuration of the test and the moment the spy was created, but I have no idea how to solve the problem. Current test configuration:
describe('Service: UsersListDataProvider', function () {
var usersListDataProvider,
userResourceStub,
deferred,
$rootScope,
$q;
beforeEach(function () {
module('UsersList');
});
beforeEach(function () {
userResourceStub = {
getUsers: function (queryParams) {
return queryParams;
}
};
module(function ($provide) {
$provide.value('UserResource', userResourceStub);
});
});
beforeEach(inject(function (_UsersListDataProvider_, _$rootScope_, _$q_) {
usersListDataProvider = _UsersListDataProvider_;
$rootScope = _$rootScope_;
$q = _$q_;
deferred = _$q_.defer();
}));
beforeEach(function () {
spyOn(userResourceStub, 'getUsers').and.returnValue(deferred.promise);
});
describe('Method: initDataProvider', function () {
it('');
});
});
Any idea ?
I'm using the ui-bootstrap modal window and I'm trying to test a method that fires that modal window. My controller:
app.controller('AddProductController', ['$scope', 'ProductsService', '$uibModal', function ($scope, ProductsService, $uibModal) {
$scope.product = {};
$scope.searchCategories = function () {
ProductsService.getRootCategories().then(function (data) {
$scope.categories = data.data;
});
$scope.modalInstance = $uibModal.open({
animation: $scope.animationsEnabled,
templateUrl: 'categoryContent.html',
controller: 'AddProductController',
scope: $scope
});
$scope.modalInstance.result.then(function (category) {
$scope.searchCategory = null;
$scope.product.category = category;
}, function () {
});
};
$scope.ok = function(){
$scope.modalInstance.close($scope.product.category);
};
$scope.cancel = function(){
$scope.modalInstance.dismiss();
}]);
And my test:
describe("Products Controller", function () {
beforeEach(function () {
module('productsController');
});
beforeEach(function () {
var ProductsService, createController, scope, rootScope,
module(function ($provide) {
$provide.value('ProductsService', {
getRootCategories: function () {
return {
then: function (callback) {
return callback({data: {name: 'category1'}});
}
};
},
});
$provide.value('$uibModal', {
open : function(){
return {
then: function (callback) {
return callback({data: {name: 'category1'}});
}
};
}
});
return null;
});
});
describe('AddProductController', function () {
beforeEach(function () {
inject(function ($controller, _$rootScope_, _ProductsService_) {
rootScope = _$rootScope_;
scope = _$rootScope_.$new();
ProductsService = _ProductsService_;
createController = function () {
return $controller("AddProductController", {
$scope: scope,
});
};
});
});
it('calling searchCategories should make $scope.categories to be defined', function () {
createController();
expect(scope.categories).not.toBeDefined();
scope.searchCategories();
expect(scope.categories).toBeDefined();
});
});
});
All my tests pass,except this one, where I get TypeError: $scope.modalInstance.result is undefined.
Any clues?
It seems you're not defining result in your mock modal. Try something like this:
$provide.value('$uibModal', {
open: function () {
return {
result : {
then: function (callback) {
return callback({ data: { name: 'category1' } });
}
}
};
}
});
Here I have :
var app = angular.module('app');
app.controller("myController", function () {
var vm = this;
vm.myFunction = function() { alert('foo'); };
});
app.animation('.animate', ["$timeout", function($timeout) {
var vm = this;
return {
addClass: function(element, className, doneFn) {
$timeout(function() {
console.log('this is displayed');
vm.myFunction(); // Doesn't work !
});
}
}
}]);
When I add a class in the template, addClass gets fired. However, vm.myFunction() doesn't, because it does not exist.
How do we do this in angular ?
Some different from yours but I thought this can help you...
in HTML
<div id="outer" ng-controller="myController"></div>
in JS
var app = angular.module('app');
app.controller('myController', function ($scope) {
$scope.myFunction = function() { alert('foo'); };
});
var scope = angular.element($("#outer")).scope();
scope.myFunction();
Modify your code as the following:
var app = angular.module('app');
app.controller('myController', function ($scope) {
var vm = this;
vm.myFunction = function() { alert('foo'); };
$scope = vm;
});
app.animation('.animate', ["$timeout", 'myController', function($timeout, myController) {
var vm = myController;
return {
addClass: function(element, className, doneFn) {
$timeout(function() {
console.log('this is displayed');
vm.myFunction(); // Doesn't work !
});
}
}
}]);
I have a simple controller with init function:
init() {
this.PositionService.getPagePosition(this.$state.params.name).then(( res ) => {
this.position = res.data.position;
})
}
And my positions service:
getPagePosition( name ) {
return this.$http.get(this.appConfig.api.positions + '/' + name);
}
My test:
describe('position list page', function() {
var scope,
$httpBackend,
controller;
beforeEach(module('module'));
beforeEach(inject(function( $controller, $rootScope, _$httpBackend_, _PositionService_ ) {
scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
controller = $controller('PositionsController', {
$scope : scope,
PositionService : _PositionService_,
});
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
describe('PositionsController', function() {
it('should get the position', function() {
$httpBackend.whenGET(/http:\/\/localhost:3000\/positions\/[a-z]+/).respond(200, {
data: {
id: 2
}
});
controller.init();
$httpBackend.flush();
expect(controller.position.id).to.equal(1);
});
});
});
My problem is that i get this error:
Error: Unexpected request: GET http://localhost:3000/api/positions/undefined
No more request expected
Why the paramater is undefiend and why i get this error?
With Jasmine and ES5 the test will look like:
angular.module('module', [])
.controller('PositionsController', function(PositionService, $state) {
this.init = function() {
PositionService.getPagePosition($state.params.name)
.then(function(res) {
this.position = res.data.position;
}.bind(this))
}
}).service('PositionService', function($http, appConfig) {
this.getPagePosition = function(name) {
return $http.get(appConfig.api.positions + '/' + name);
}
});
describe('Position list page', function() {
var scope,
$httpBackend,
controller;
beforeEach(module('module'));
beforeEach(function() {
angular.module('module').value('appConfig', {
api: {
positions: 'http://localhost:3000/positions'
}
})
})
beforeEach(inject(function($controller, $rootScope, _$httpBackend_, _PositionService_) {
scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
controller = $controller('PositionsController', {
$scope: scope,
PositionService: _PositionService_,
$state: {
params: {
name: 'someParamValue'
}
}
});
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
describe('PositionsController:', function() {
it('Gets the position - init()', function() {
var stubId = 2
$httpBackend.whenGET(/http:\/\/localhost:3000\/positions\/[a-z]+/).respond(200, {
position: {
id: stubId
}
});
controller.init();
expect(controller.position).not.toBeDefined();
$httpBackend.flush();
expect(controller.position).toBeDefined();
expect(controller.position.id).toEqual(stubId);
});
});
});
<link href="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine.css" rel="stylesheet" />
<script src="//safjanowski.github.io/jasmine-jsfiddle-pack/pack/jasmine-2.0.3-concated.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular-mocks.js"></script>
This is my angular controller :-
angular.module('authoring-controllers', []).
controller('NavCtrl', function($scope, $location, BasketNavigationService) {
$scope.test= function() {
$scope.testVar = BasketNavigationService.showBasketList();
};
});
TEST class
describe('NavCtrl', function() {
var scope, $location, createController;
beforeEach(inject(function ($rootScope, $controller, _$location_) {
$location = _$location_;
scope = $rootScope.$new();
createController = function() {
return $controller('NavCtrl', {
'$scope': scope
});
};
}));
it('should create $scope.testVar when calling test',
function() {
expect(scope.testVar).toBeUndefined();
scope.test();
expect(scope.testVar).toBeDefined();
});
});
Getting an error when i run that test case :- scope.test() is undefined..
If i removed BasketNavigationService functionality from controller then it is working..
Please help me to solve that karma test case.
here is the working demo , hope it helps.
problem was with injecting the dependencies.
//--- CODE --------------------------
(function(angular) {
// Create module
var myApp = angular.module('myApp', []);
// Controller which counts changes to its "name" member
myApp.controller('MyCtrl', ['$scope', 'BasketNavigationService',
function($scope, BasketNavigationService) {
$scope.test = function() {
$scope.testVar = BasketNavigationService.showBasketList();;
};
}
]);
})(angular);
// ---SPECS-------------------------
describe('myApp', function() {
var scope,
controller;
beforeEach(function() {
module('myApp');
});
describe('MyCtrl', function() {
beforeEach(inject(function($rootScope, $controller) {
scope = $rootScope.$new();
controller = $controller('MyCtrl', {
'$scope': scope,
'BasketNavigationService': {
showBasketList: function() {
return null;
}
}
});
}));
it('should create $scope.testVar when calling test',
function() {
expect(scope.testVar).toBeUndefined();
scope.test();
// scope.$digest();
expect(scope.testVar).toBeDefined();
});
});
});
// --- Runner -------------------------
(function() {
var jasmineEnv = jasmine.getEnv();
jasmineEnv.updateInterval = 1000;
var htmlReporter = new jasmine.HtmlReporter();
jasmineEnv.addReporter(htmlReporter);
jasmineEnv.specFilter = function(spec) {
return htmlReporter.specFilter(spec);
};
var currentWindowOnload = window.onload;
window.onload = function() {
if (currentWindowOnload) {
currentWindowOnload();
}
execJasmine();
};
function execJasmine() {
jasmineEnv.execute();
}
})();
<script src="http://jasmine.github.io/1.3/lib/jasmine.js"></script>
<script src="http://jasmine.github.io/1.3/lib/jasmine-html.js"></script>
<script src="https://code.angularjs.org/1.2.9/angular.js"></script>
<script src="https://code.angularjs.org/1.2.9/angular-mocks.js"></script>
<link href="http://jasmine.github.io/1.3/lib/jasmine.css" rel="stylesheet" />
fiddle : http://jsfiddle.net/invincibleJai/pf1deoom/1/
Please change your createController setup to:
createController = function() {
return $controller('NavCtrl', {
'$scope': scope,
'$location':$location,
'BasketNavigationService':{showBasketList: function(){return 'test'} }
});
};
You are not injecting all the dependencies.
I have injected dummy BasketNavigationService, you can inject the real one.
Have you tried running createController();? I don't think your NavCtrl controller gets mocked.