AngularJS Jasmine unit test how to inject parent scope - angularjs

I have a nested controller as below:
<div ng-controller="ParentController">{{ data.value }}
<div ng-controller="ChildController">{{ data.value }}</div>
</div>
app.controller('ParentController', function ($scope) {
$scope.data = {
value: "A"
}
});
My child controller sets the parent scope as below:
app.controller('ChildController', function ($scope) {
$scope.data.value = "B";
});
My Jasmine unit test is:
describe('ChildController', function () {
var scope, $rootScope;
beforeEach(inject(function ($controller, _$rootScope_) {
$rootScope = _$rootScope_;
scope = $rootScope.$new();
$controller('ChildController', { $scope: scope});
scope.$digest();
}));
it('should change parent scope', function () {
expect(scope.data.value).toEqual("B");
});
});
The test results in "Cannot read property 'value' of undefined".
How do I unit test a child controller that uses a parent scope?

It really depends on what you want to test. If you want to assert that the child controller changes the value during its initialization then just setup the value for the test to change. You don't need to test Angular's scope hierarchy.
So, I'd suggest just to do this:
describe('ChildController', function () {
var scope, $rootScope;
beforeEach(inject(function ($controller, _$rootScope_) {
$rootScope = _$rootScope_;
scope = $rootScope.$new();
//setup
scope.data = { value: "A" };
$controller('ChildController', { $scope: scope});
scope.$digest();
}));
it('should change parent scope', function () {
expect(scope.data.value).toEqual("B");
});
});

First initialize $scope.data={};
app.controller('ChildController', function ($scope) {
$scope.data={};
$scope.data.value = "B";
});

Related

scope keeps being undefined in angularjs unit-testing

I am trying to unit-testing for following code, I wrote following code for unit-testing like below, I have tried so many ways to work, but I keep getting error:
'Cannot read property 'num' of undefined'
I do not know why scope is not properly set. If you have any idea about it, can you please give some advices?
var angular = require('angular');
require('angular-mocks');
describe('test directive', function () {
let $rootScope;
let $compile;
let scope;
let newScope;
let element;
beforeEach(angular.mock.inject(function (_$compile_, _$rootScope_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
}));
describe('test directive', function () {
beforeEach(function () {
newScope = $rootScope.$new();
element = $compile('<test-directive></test-directive>')(newScope);
newScope.$digest();
scope = element.isolateScope();
});
fit('scope initialized', function () {
expect(scope.num).toEqual(1);
});
});
});
module.exports = module.directive('testDirective', ['$rootScope', '$scope', function($rootScope, $scope) {
return {
template: require('./test.html'),
restrict: 'E',
controller: [
'$scope',
function ($scope) {
$scope.num = 1
$scope.sum = function(a, b) {
return a + b;
}
}]
}
}]);
The scope variable is undefined because the directive being tested does not have an isolate scope. Instead, use the scope of the element:
beforeEach(function () {
newScope = $rootScope.$new();
element = $compile('<test-directive></test-directive>')(newScope);
newScope.$digest();
̶s̶c̶o̶p̶e̶ ̶=̶ ̶e̶l̶e̶m̶e̶n̶t̶.̶i̶s̶o̶l̶a̶t̶e̶S̶c̶o̶p̶e̶(̶)̶;̶
scope = element.scope();
});
fit('scope initialized', function () {
expect(scope.num).toEqual(1);
});
Be aware that directive has a fatal flaw. It can only use it once within a given scope.

How to write test-case for Directive with in Directive using jasmine

I wrote a test for my directive using jasmine testcase framework with karma testcase runner.
In my project ,I am already having one directive called
<parent-directive></parent-directive>
and i tried to include that parent directive into another one called
<child-directive></child-directive>.
Parent directive elements are converted as components called SampleComponents and included in the child directive
Sample.js
'use strict'
angular.module('Sample')
.directive('SampleHeader', SampleHeader)
function SampleHeader () {
return {
restrict: 'A',
templateUrl: 'header/header.html',
scope: {},
controller: function ($scope) {
$scope.logoutHeader = function () {
console.log('Logout call back')
require('electron').remote.app.quit()
}
}
}
}
SampleSpec.js
describe('SampleHeader', function () {
var $compile, $rootScope, elements, scope, controller
beforeEach(module('Sample'))
beforeEach(module('SampleComponenets'))
beforeEach(module('ngAnimate'))
beforeEach(module('ngRoute'))
beforeEach(module('ngMaterial'))
beforeEach(module('ngCookies'))
beforeEach(module('datatables'))
beforeEach(inject(function (_$compile_, _$rootScope_, _$q_,_$controller_) {
deferred = _$q_.defer()
$compile = _$compile_
$rootScope = _$rootScope_
controller = _$controller_
scope = $rootScope.$new()
elements = angular.element('<sample-header></sample-header>')
$compile(elements)($rootScope.$new())
$rootScope.$digest()
controller = elements.controller('SampleHeader')
scope = elements.isolateScope() || elements.scope()
scope.$digest()
}))
it('should check logoutHeader is called', function () {scope.logoutHeader()
})
})
restrict: 'A' -> it seems you created a attribute directive so you should compile the directive as a attribute (like, elements = angular.element('<div sample-header></div>')).
describe('SampleHeader', function () {
var $compile, $rootScope, elements, scope, controller
beforeEach(module('Sample'))
beforeEach(module('SampleComponenets'))
beforeEach(module('ngAnimate'))
beforeEach(module('ngRoute'))
beforeEach(module('ngMaterial'))
beforeEach(module('ngCookies'))
beforeEach(module('datatables'))
beforeEach(inject(function (_$compile_, _$rootScope_, _$q_,_$controller_) {
deferred = _$q_.defer()
$compile = _$compile_
$rootScope = _$rootScope_
controller = _$controller_
scope = $rootScope.$new()
elements = angular.element('<div sample-header></div>')
$compile(elements)($rootScope.$new())
$rootScope.$digest()
controller = elements.controller('SampleHeader')
scope = elements.isolateScope() || elements.scope()
scope.$digest()
}))
it('should check logoutHeader is called', function () {scope.logoutHeader()
})
})

how to get access to controller scope when testing directive with isolate scope

I have directive with isolate scope and controller, Is it right to write unit tests for directive and test some functional in controller scope?
And if it right, how i can get access to controller scope?
angular.module('myApp').directive('myDirecive', function () {
return {
templateUrl: 'template.html',
scope: {
data: '='
},
controller: function ($scope) {
$scope.f= function () {
$scope.value = true;
};
},
link: function ($scope) {
// some logic
});
}
};
})
describe('myDirective', function () {
'use strict';
var element,
$compile,
$rootScope,
$scope;
beforeEach(module('myApp'));
beforeEach(inject(function (_$compile_, _$rootScope_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
element = compileElement();
}));
function compileElement() {
var element = angular.element('<my-directive></my-directive>');
var compiledElement = $compile(element)($scope);
$scope.$digest();
return compiledElement;
}
it('should execute f', function () {
$scope.f();
expect($scope.val).toBe(true); // there we can access to isolate scope from directive;
});
});
Controller is given the isolated scope created by the directive. Your $scope, that you pass into compile function, is used as a parent for the directive's isolate scope.
Answering how to test it, you have two options:
Access the isolated scope from the element:
var isolated = element.isolateScope();
For that, you need to have $compileProvider.debugInfoEnabled(true);
Access the isolated scope from your scope:
var isolated = $scope.childHead;
Try this
describe('myDirective', function () {
'use strict';
var element,
dirCtrlScp,
$compile,
$rootScope,
$scope;
beforeEach(module('myApp'));
beforeEach(inject(function (_$compile_, _$rootScope_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
element = compileElement();
}));
function compileElement() {
var element = angular.element('<my-directive></my-directive>');
var compiledElement = $compile(element)($scope);
dirCtrlScp = element.controller;
$scope.$digest();
return compiledElement;
}
it('should execute f', function () {
dirCtrlScp.f();
expect(dirCtrlScp.value).toBe(true); // there we can access to isolate scope from directive;
});
});
Have a look at this Article

Testing AngularJS directive scope undefined

I'm trying to test one of my directives and I can't work out why my scope object is undefined.
define(["require", "exports", 'angular', 'directives', "angularMocks", "chartApp"], function (require, exports, angular, Directives) {
"use strict";
describe('board controls', function () {
describe('task filter', function () {
var $compile;
var $rootScope;
var scope;
var element;
beforeEach(angular.mock.module('chartApp'));
beforeEach(angular.mock.module('partials/directives/board-controls.html'));
beforeEach(inject(function (_$compile_, _$rootScope_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
scope = $rootScope.$new();
expect(scope).toBeDefined();
element = $compile('<board-controls></board-controls>')(scope);
scope.$digest();
}));
it('displays modal', function () {
scope.showChildFilters();
});
});
});
});
In the it('displays modal')... part Karma outputs:
TypeError: undefined is not an object (evaluating 'scope.showChildFilters')
But in the beforeEach(...) part it seems to be working. I just can't see why this doesn't work.
You need to change to this
it('displays modal', function () {
//scope.showChildFilters();
var isolateScope = element.isolateScope(); //I prefer to name isolateScope
isolateScope.$apply() //cause scope to digest and watch and all that
isolateScope.showChildFilters();
});
This is a very detailed answer to your question as well.
Testing element directive - can't access isolated scope methods during tests

How to test change on scope executed in directive controller

I have directive myItem. I want to change one property that is passed from parent to directive, so I use controller in myItem where I divide value by 60.
Everything works fine on the website.
Directive
define(['angular',
'core',
'ui-bootstrap',
],
function(angular, coreModule, uiBootStrap) {
'use strict';
function myItemDirective(APPS_URL) {
return {
restrict: 'E',
replace: 'true',
scope: {
item: '='
},
templateUrl: APPS_URL.concat('item.tpl.html'),
controller: ['$scope', function($scope) {
$scope.item.property = Math.round($scope.item.property / 60);
}]
};
}
return angular.module('apps.myItemModule', [coreModule.name])
.directive('myItem', ['APPS_URL', myItemDirective]); });
Now I would like to write a test where I can check that rendered value in directive is value passed from parent divided by 60.
Unit Test
define([
'angular',
'angularMocks',
'apps/directives/mydirective' ], function (angular, mocks, myItemDirective) {
var $httpBackend, $controller, $rootScope, $scope, directive,
item = {
property: 60
};
describe('my directive', function () {
beforeEach(function() {
mocks.module(myItemDirective.name);
mocks.inject(function(_$rootScope_, $injector, $window) {
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
$compile = $injector.get('$compile');
compileDirective();
});
});
afterEach(function() {
$scope.$destroy();
element.remove();
});
function compileDirective() {
element = angular.element('<my-item item=' + JSON.stringify(item) + '></my-item>');
directive = $compile(element)($scope);
$scope.$digest();
directiveScope = element.isolateScope();
}
it('test', function(){
// console.log(element.find('#item-holder').innerText)
// > 60
expect(element.find('#item-holder').innerText).toEqual(Math.round(item.propert/60));
// this will fail because item.property was not divided by 60
});
}); });
Problem
I am not able to render directive in unit test with value divided by 60. I can see in console that controller in directive has been called but the value is not changed.
The problem was related to tests using the same reference to object item.
To fix this:
moved item to beforeEach
changed the way to create element
changed the way to get directive scope
use $scope.$apply()
So test looks like:
define([
'angular',
'angularMocks',
'apps/directives/mydirective' ], function (angular, mocks, myItemDirective) {
var $httpBackend, $controller, $rootScope, $scope, directive,
item;
describe('my directive', function () {
beforeEach(function() {
item = {
property: 60
};
mocks.module(myItemDirective.name);
mocks.inject(function(_$rootScope_, $injector, $window) {
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
$compile = $injector.get('$compile');
compileDirective();
});
});
afterEach(function() {
$scope.$destroy();
element.remove();
});
function compileDirective() {
$scope.item = item;
element = angular.element('<my-item item="item"></my-item>');
directive = $compile(element)($scope);
$scope.$apply();
directiveScope = directive.isolateScope();
}
it('test', function(){
// console.log(element.find('#item-holder').innerText)
// > 60
expect(element.find('#item-holder').innerText).toEqual(Math.round(item.propert/60));
// this will fail because item.property was not divided by 60
});
}); });

Resources