I'm trying to test a directive using karma and jasmine. I'm using angular 1.4 and I searched different things here in SO and throuhg internet but I can't make it work.
var angular = require('angular');
module.exports = angular.module('myApp.ui.apps.servicesObject.list', [])
.directive('servicesObjectList', function(){
return {
restrict: 'E',
replace: true,
scope: true,
bindToController: {
services: '=',
selectedServices: '='
},
templateUrl: 'app/ui/apps/services/directives/services.html',
controllerAs: 'servicesListCtrl',
controller: 'ServicesListController'
}
})
.controller('ServicesListController', require('./servicesListController'));
This is how I'm trying to test it.
describe('Service app test, listDirective' , function(){
var element, scope, controller;
beforeEach(function(){
angular.mock.module('templates');
angular.mock.module('myApp.ui.apps.servicesObject.list', function($provide) {
$provide.value('gettextCatalog', { getString: function(){}});
$provide.value('translateFilter', function(){});
});
});
beforeEach(inject(function($rootScope, $compile, $controller){
scope = $rootScope;
scope.services= _servicesMock_;
element = '<services-object-list selected-services="[]" services="services"></services-object-list>';
$compile(element)(scope);
scope.$digest();
controller = $controller('ServicesListController', {$scope: scope});
console.log(controller.getServices());
}));
it ("First test", function(){
expect(true).toBe(true);
});
});
The problem that I have is that services is not binding in my controller, only in the scope. What I'm doing wrong? If I do console(controller.getServices()). It returns me undefined instead of the services that I pass as attribute. My production code is working as expected but not tests.
Thank you very much!
After some hours I just discover a new feature added in angular 1.3 to make binding in unitTesting easier. Here is the thread of the discussion https://github.com/angular/angular.js/issues/9425
Basically a third argument is added to the controller constructor service where you can pass the data that is bind to controller.
So the unitTest configuration will be like this.
describe('Service app test, listDirective' , function(){
var element, scope, controller;
beforeEach(function(){
angular.mock.module('templates');
angular.mock.module('myApp.ui.apps.servicesObject.list', function($provide) {
$provide.value('gettextCatalog', { getString: function(){}});
$provide.value('translateFilter', function(){});
});
});
beforeEach(inject(function($rootScope, $compile, $controller){
var data = {
services: _servicesMock_
};
scope = $rootScope;
scope.services= _servicesMock_;
element = '<services-object-list selected-services="[]" services="services"></services-object-list>';
$compile(element)(scope);
scope.$digest();
controller = $controller('ServicesListController', {$scope: scope}, data);
console.log(controller.getServices());
}));
it ("First test", function(){
expect(true).toBe(true);
});
});
Related
I have created a scope method inside my controller which is executing when a button is pressed. I am writing unit test cases for the same. I have injected my module in beforeEach block and created spyon my scope function and then using it in 'it' method and checking whether it is called or not. But getting an error as a method not found.
Controller
'use strict';
angular.module('myApp.view1', ['ngRoute'])
.config(['$routeProvider', function ($routeProvider) {
$routeProvider.when('/view1', {
templateUrl: 'view1/view1.html',
controller: 'View1Ctrl'
});
}])
.controller('View1Ctrl', ['$scope',View1Ctrl])
function View1Ctrl($scope) {
$scope.user = {
name: '',
last: ''
}
$scope.showFormData = function() {
$scope.formData = $scope.user.name + $scope.user.last;
}
}
spec.js
'use strict';
describe('myApp.view1 module', function () {
var $controller, $rootScope;
beforeEach(module('myApp.view1'));
beforeEach(inject(function (_$controller_, _$rootScope_) {
$controller = _$controller_;
$rootScope = _$rootScope_;
}));
describe('view1 controller', function () {
var $scope, controller, formData;
beforeEach(function () {
$scope = $rootScope.$new();
controller = $controller('View1Ctrl', {
$scope: $scope
});
spyOn(controller, 'showFormData');
});
it('should check for the show form details', function () {
$scope.user.name = "Sandeep";
$scope.user.last = "Gupta";
expect($scope.showFormData).toHaveBeenCalled();
expect($scope.user.name + $scope.user.last).toEqual(firstname);
});
});
});
Need help to resolve this issue.
It looks like you're trying to spy on the showFormData method of the controller:
spyOn(controller, 'showFormData');
However, the showFormData doesn't exist on the controller, it's a method of the controller's scope.
You'll need to use:
spyOn($scope, 'showFormData');
It's also important to know that you need to use the same object to both spyOn and expect(...).toHaveBeenCalled(). In your case you where spying on controller.showFormData(), yet expecting $scope.showFormData() to have been called.
I am having a few problems accessing my controller on a directive that I am trying to unit test with jasmine and karma testrunner. The directive looks like this:
directive
angular.module('Common.accountSearch',['ngRoute'])
.directive('accountSearch', [function() {
return {
controllerAs: 'ctrl',
controller: function ($scope, $element, $routeParams, $http) {
this.setAccount = function () {
var response = { AccountId : $scope.ctrl.searchedAccount.AccountId }
$scope.callback(response)
}
this.getAccounts = function(searchText){
return $http.get('/api/CRMAccounts', {
params: {
retrievalLimit: 10,
search: searchText
}
}).then(function(response){
return response.data;
});
}
},
scope : {
config : '=',
values : '=',
callback : '='
},
templateUrl : '/common/components/account-search/account-search.html',
restrict : 'EAC'
}
}]);
This here is the test case file so far I believe all is in order and correct (I hope):
test case file:
describe("Account search directive logic tests", function (){
var element,$scope,scope,controller,template
beforeEach(module("Common.accountSearch"))
beforeEach(inject( function (_$compile_, _$rootScope_,_$controller_,$templateCache) {
template = $templateCache.get("components/account-search/account-search.html")
$compile = _$compile_;
$rootScope = _$rootScope_;
$controller = _$controller_;
scope = $rootScope.$new();
element = $compile(template)(scope)
ctrl = element.controller
scope.$digest();
// httpBackend = _$httpBackend_;
}));
it(" sets the account and calls back.", inject(function () {
console.log(ctrl)
expect(ctrl).toBeDefined()
}));
//httpBackend.flush()
});
I have managed to print the controller of the directive ( I think) to the console which returns the following ambiguous message:
LOG: function (arg1, arg2) { ... }
I cannot access any of the functions or properties on the directive as they are all returning "undefined", what am I doing wrong?
Controllers for directives are actually fully injectable - instead of providing a constructor, you can just refer to the controller by name. See the directive definition object docs for Angular here: https://docs.angularjs.org/api/ng/service/$compile#directive-definition-object
In your case where you want to unit test the controller you'd just do it like this:
common.accountSearch.js
angular.module('Common.accountSearch', [])
.directive('accountSearch', [function () {
return {
controller: 'accountSearchCtrl',
scope: {
config : '=',
values : '=',
callback : '='
},
templateUrl : '/common/components/account-search/account-search.html',
restrict: 'EAC'
}
}])
.controller('accountSearchCtrl', ['$scope', function ($scope) {
$scope.setAccount = function () {
var response = {
AccountId: $scope.ctrl.searchedAccount.AccountId
};
$scope.callback(response);
}
$scope.getAccounts = function (searchText) {
// Code goes here...
}
}]);
common.accountSearch-spec.js
describe("Account search directive logic tests", function () {
var controller, scope;
beforeEach(module("Common.accountSearch"));
beforeEach(inject(function (_$controller_, _$rootScope_) {
$rootScope = _$rootScope_;
scope = $rootScope.$new();
controller = _$controller_('accountSearchCtrl', { '$scope': scope });
}));
it(" sets the account and calls back.", function () {
expect(controller).toBeDefined();
});
});
This way you can just inject your controller directly into your jasmine tests like any of your other controllers.
Hope this helps.
So close!
element.controller is a function and needs to be passed the name of the directive which you're attempting to get the controller for. In this case it would be
ctrl = element.controller("accountSearch");
element.controller is an additional method to AngularJS jqLite, so when you log it you see jqLite method .toString(). You should call it and get a directive controller. Element controller manual
It's a very simple test.. and it's not passing.. If someone can throw some light into this :)
This is the controller code (part of it) that needs to be tested
AppCtrl
$scope.requestAuthorization = function() { requestAuthorization(); };
if ($stateParams.requestAuthorization === true) {
console.log('$stateParams.requestAuthorization');
$scope.requestAuthorization();
}
function requestAuthorization() {
console.log('requestAuthorization()');
// more code here..
}
Test
describe('AppCtrl', function() {
var AppCtrl, $rootScope, $scope, $stateParams;
beforeEach(module('myapp'));
// disable ionic cache to avoid GET errors
beforeEach(module(function($provide, $urlRouterProvider) {
$provide.value('$ionicTemplateCache', function() {});
$urlRouterProvider.deferIntercept();
}));
beforeEach(inject(function($controller, _$rootScope_, _$injector_, _$stateParams_) {
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
$stateParams = _$stateParams_;
AppCtrl = $controller('AppCtrl',{
$scope: $scope
});
spyOn($scope, 'requestAuthorization');
$stateParams.requestAuthorization = true;
}));
it('$stateParams.requestAuthorization should be defined', function() {
expect($stateParams.requestAuthorization).toBeDefined();
});
it('$scope.requestAuthorization should be defined', function() {
expect($scope.requestAuthorization).toBeDefined();
});
// this test is not passing..
it('should call requestAuthorization', function() {
expect($scope.requestAuthorization).toHaveBeenCalled();
});
});
The function is actually being called, I can see the console.log in the console, but it's not passing.
Easy tests, all passing.. except the last one..
Thanks for your time :)
NOTE: There is a $stateParams.requestAuthorization, and a $scope.requestAuthorization. First one is boolean, the other a function, the function is not passing.
In your beforeEach block, define the $stateParams before instanciate the Controller.
beforeEach(inject(function($controller, _$rootScope_, _$injector_, _$stateParams_) {
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
$stateParams = _$stateParams_;
$stateParams.requestAuthorization = true;
AppCtrl = $controller('AppCtrl',{
$scope: $scope,
$stateParams: $stateParams
});
spyOn($scope, 'requestAuthorization');
}));
I am trying to get 100% test coverage for a directive. The directive has a controller with a function that uses the window.confirm method.
'use strict';
(function() {
angular
.module('app')
.directive('buttonToggle', buttonToggle);
function buttonToggle() {
var buttonToggleController = ['$scope', function($scope) {
$scope.toggle = function() {
var confirmResponse = (window.confirm('Are you sure?') === true);
if(confirmResponse) {
$scope.on = !$scope.on;
}
return $scope.on;
};
}];
return {
restrict: 'E',
templateUrl: 'client/modules/buttonToggle/buttonToggle.html',
replace: true,
scope: {
on: '='
},
controller: buttonToggleController
};
}
})();
I have tested to make sure that everything is defined, but I am not able to enter the if statement in the controller's $scope.toggle method.
describe('The buttonToggle directive', function() {
var $compile,
$scope,
btElement = '<button-toggle></button-toggle>',
compiledElement,
window,
confirm,
btElementPath = 'client/modules/buttonToggle/buttonToggle.html',
btController;
beforeEach(module('app'));
beforeEach(module(btElementPath));
beforeEach(inject(function(_$compile_, _$rootScope_, $templateCache, $window) {
$compile = _$compile_;
window = $window;
spyOn(window, 'confirm');
$scope = _$rootScope_.$new();
var template = $templateCache.get(btElementPath);
$templateCache.put(btElementPath, template);
var element = angular.element(btElement);
compiledElement = $compile(element)($scope);
$scope.$digest();
btController = element.controller('buttonToggle', {
$window: window
});
scope = element.isolateScope() || element.scope();
}));
it('should be defined', function() {
expect(compiledElement.html()).toContain('btn');
});
describe('buttonToggle controller', function() {
it('should be defined', function() {
expect(btController).not.toBeNull();
expect(btController).toBeDefined();
});
describe('toggle', function() {
it('should be defined', function() {
expect(scope.toggle).toBeDefined();
});
it('should confirm the confirmation dialog', function() {
scope.toggle();
expect(window.confirm).toHaveBeenCalled();
});
});
});
});
I am guessing it has something to do with mocking the $window service, but I'm not sure if I will be able to test that since it isn't declared globally. So, is the controller's function fully "unit testable"? If not, should I write the directive's controller in a separate file and use angular.module.controller? If yes, then how am I able to test it, or what am I missing?
Use angular's $window service instead of window directly, which is what you are doing in your test but not in your directive.
Then you can mock any of its functions:
spyOn($window, 'confirm').and.returnValue(false);
Having trouble reaching my directive's scope for a unit test. Getting generic compile errors when trying to run the unit test.
My app compiles (gulp) and runs fine, and I can unit test non-directive's just fine as well. I am just not sure how to get the test's to work with a directive, I am just trying other people's solutions with some educated guesses.
Main page's HTML and JS
<div company-modal="companyOptions" show-close="false"></div>
.
(function() {
'use strict';
angular
.module('app')
.controller('companyModalCtrl', ['$scope', selectionPage]);
function selectionPage($scope) {
$scope.companyOptions = {};
}
})();
Here is the first portion of my directive (it is very big so just including the important-top part.
(function() {
'use strict';
angular
.module('app')
.directive('companyModal',
companyModal
);
function companyModal() {
return {
restrict: 'A',
replace: false,
transclude: true,
templateUrl: '/src/login/company.html',
scope: {
options: '=companyModal',
showClose: '='
},
bindToController: true,
controller: companySelection,
controllerAs: 'csmvm'
};
}
companySelection.$inject = ['$state'];
function companySelection($state) {
var csmvm = this;
var url;
csmvm.emailList = [];
Here is my attempt at the Unit Test
'use strict';
describe('Company', function () {
var scope;
var controller;
var elem;
beforeEach(module('app'));
beforeEach(inject(function ($controller, $rootScope, $compile) {
scope = $rootScope.$new();
elem = angular.element('<div company-modal="companyOptions" show-close="false"></div>');
$compile(elem)($rootScope);
/*
controller = elem.controller('companyModal as csmvm');
controller = $controller(controller,
{
$scope : scope
});
*/
controller = $controller(elem.controller('companyModal as csmvm'), {
$scope: scope
});
scope.csmvm.emailList.email = "Bob#gmail.com";
}));
describe('Invite', function () {
it('should be an array for emailList', function () {
expect(scope.csmvm.emailList).to.be.an('array');
});
});
});
My problem (and sorry being very undescriptive) is that I just keep getting run-time errors from the test. Such as:
Failed Tests:
Company
"before each" hook: workFn
18) the object { "message": "'undefined' is not an object (evaluating '(isArray(Type) : Type).prototype)".
And again, my app compiles (gulp) and runs fine, and I can unit test non-directive's just fine as well
You need to run expectations towards the isolated scope of your directive.
Right now, scope is referring to the scope where the directive was compiled.
But your directive creates a new isolate scope, and you are not running expectations towards it.
Two ways of doing it:
function isolateScope (el) {
return el.isolateScope();
}
/** or **/
var el, scope, isolateScope;
beforeEach(inject(function ($compile, $rootScope) {
scope = $rootScope.$new();
el = $compile('<directive></directive>')(scope);
scope.$digest();
isolateScope = el.isolateScope();
});
With all of that said, I would remove the controller out of your directive spec suite and test that in isolation. It makes for far cleaner / modular unit tests. Try to break them up as best you can.