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' } });
}
}
};
}
});
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 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;
}
});
})();
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>
app.admin.routes.js
(function () {
'use strict';
angular
.module('app.admin')
.run(appRun);
appRun.$inject = ['routeHelper', 'app.core.services.notificationService'];
function appRun(routeHelper, notificationService) {
debugger;
routeHelper.configureRoutes(getRoutes());
function getRoutes() {
return [
{
state: 'admin',
url: '/admin',
templateUrl: 'app/features/admin/admin.html',
controller: 'Admin as vm',
resolve: {
// signalRConnection: function() {
// return notificationService.onReady;
// }
},
settings: {
navigation: {
group: "application",
label: "Admin",
//label: "navigation.admin",
icon: "fa-lock",
order: 2
}
//content: '<i class="fa fa-lock"></i> Admin'
}
}
];
}
}
})();
admin.js
(function () {
'use strict';
angular
.module('app.admin')
.controller('Admin', Admin);
Admin.$inject = ['logger'];
function Admin(logger) {
/*jshint validthis: true */
var vm = this;
vm.title = 'Admin';
activate();
function activate() {
logger.info('Activated Admin View');
}
}
})();
adminctrlSpec.js
describe("AdminController", function () {
var _logger;
beforeEach(function() {
module("app.admin", function ($provide) {
$provide.value('routeHelper', {
configureRoutes: function(routes) {
}
});
$provide.value('app.core.services.notificationService', {
signalRConnection: function () {
}
});
});
});
beforeEach(function () {
inject([
'logger', function (logger) {
debugger;
}
]);
});
it("asd", function() {
debugger;
});
});
I am able to use logger in all of my other Specs. But unable to inject for admin.js.
beforeEach(function () {
> inject([
> 'logger', function (logger) {
> debugger;
> }
> ]);
> });
Error: [$injector:unpr] Unknown provider: loggerProvider <- logger
(function () {
'use strict';
angular.module('app', [
'app.admin',
'app.modelling',
'app.layout'
]);
})();
Problem solved .i forgot to add beforeEach(function () { module("app"); });
I have my custom angular service, which has one method that handles with $scope.$watch, is it possible to unit test it?
angular.module('moduleName', []).factory('$name', function () {
return {
bind: function ($scope, key) {
$scope.$watch(key, function (val) {
anotherMethod(key, val);
}, true);
},
...
};
});
Solution that I've found seems to be very easy and nice:
beforeEach(function () {
inject(function ($rootScope) {
scope = $rootScope.$new();
scope.$apply(function () {
scope.value = true;
});
$name.bind(scope, 'value');
scope.$apply(function () {
scope.value = false;
});
});
});
beforeEach(function () {
value = $name.get('value');
});
it('should have $scope value', function () {
expect(value).toBeFalsy();
});