cannot use $rootScope.$new() in testing directive - javascript

I have a unit test for my directive:
describe('Directive: ov-tabs', function() {
var $compile, scope,subScope, element, template, doc,timeout,window;
beforeEach(module('ngnms.ui.tabs','layout.html'));
beforeEach(inject(function(_$compile_, $rootScope,$templateCache, $timeout,$document, $window) {
template = $templateCache.get('layout.html');
$templateCache.put('template/tab-layout.html', template);
$compile = _$compile_;
timeout = $timeout;
window = angular.element($window);
doc = $document;
scope = $rootScope.$new();
var html =
'<ov-tabset ov-tabs="items" on-tab-selected="tabSelectCallback" on-tab-closed="tabClosedCallback"></ov-tabset>';
element = angular.element(html);
$(element).appendTo($('body'));
//------------------------------------------init scope
//add 12 items to scope
//-------------------------------------------end init scope
$compile(element)(scope);
scope.$digest();
}));
it('some thing true',function(){});
var lis = element.find('li');
expect(lis.length).toEqual(12);
})
The result throw error for interact with DOM
If i change "scope = $rootScope.$new();" to "scope = $rootScope;" they'll work fine!
I want test sope to $destroy(). Because $rootScope cannot invoke $destroy()
ERRORS log here!

Related

Karma/Jasmine test failing on undefined scope

I'm trying to create first unit testing for one of my controllers.
My Karma/Jasmine simple test for a controller looks like
describe('RegistrationController', function() {
beforeEach(module('myApp'));
var $controller, $rootScope;
beforeEach(inject(function(_$controller_, _$rootScope_){
// The injector unwraps the underscores (_) from around the parameter names when matching
$controller = _$controller_;
$rootScope = _$rootScope_;
}));
describe('Registration $scope.submit', function() {
it('submits registration and sets step to 1', function() {
var $scope = $rootScope.$new();
var controller = $controller('RegistrationController', { $scope: $scope });
$scope.submit();
expect($scope.step).toEqual(1);
});
});
});
which tests that part of the controller
$scope.submit = function () {
$scope.step = 1; //wizard started
var userName = $scope.user.name;
var userNick = $scope.user.nick;
$location.path('localization');
}
but when I run that unit test it fails on user.name and user.nick with an error
TypeError: Cannot read property 'name' of undefined
or
TypeError: Cannot read property 'nick' of undefined
when both values are captured by the form and works fine on the controller side.

How to unit test a function in my case

I am trying to create a unit test for the child controller
In my child controller, I called a function in the parent
child controller
$scope.clickMe = function(){
$scope.parentMethod();
})
Parent controller
$scope.parentMethod = function(item){
//do something with parent
})
Unit test
var childCtrl;
beforeEach(module('myApp'));
beforeEach(inject(function (_$controller_, _$rootScope_) {
scope = _$rootScope_.$new();
childCtrl = _$controller_('childCtrl', {
$scope: scope
});
}));
describe('test parent', function() {
it('should call parent', function() {
$scope.clickMe();
$httpBackend.flush();
});
});
});
I am getting
TypeError: 'undefined' is not a function (evaluating '$scope.parentMethod()')
I am not sure how to fix this. Can anyone help me about it? Thanks a lot!
You should mock the method in scope for sake of testing child controller
scope = _$rootScope_.$new();
scope.parentMethod = function noop(){};
childCtrl = _$controller_('childCtrl', {
$scope: scope
});
For testing noop should be replaced with a spy. Syntax will depend upon the engine you're using Jasmine or Sinon. This way in test it will be possible to verify that parentMethod was called.

AngularJS : How to create a two-way data binding between two isolated controllers and a shared service?

I am trying to create a two-way data binding between two isolated controllers and a shared service (which provides another isolated scope):
app.factory("sharedScope", function($rootScope) {
var scope = $rootScope.$new(true);
scope.data = "init text from factory";
return scope;
});
app.controller("first", function($scope, sharedScope) {
$scope.data1 = sharedScope.data;
});
app.controller("second", function($scope, sharedScope) {
$scope.data2 = sharedScope.data;
});
Fiddle: http://jsfiddle.net/akashivskyy/MLuJA/
When the application launches, data1 and data2 are correctly updated to the init text from factory, but later, if I change any of them, those changes are not reflected throughout those three scopes.
How can I bind them?
P.S. If there's a better way than returning a scope and still having access to event and observing functionalities (without basically re-writing them), let me know. :)
Fixed it. References will be lost if you are using primitives, as in your fiddle.
Check this:
Updated fiddle
app.factory("sharedScope", function($rootScope) {
var scope = $rootScope.$new(true);
scope.data = {text: "init text from factory"};
return scope;
});
app.controller("first", function($scope, sharedScope) {
$scope.data1 = sharedScope.data;
});
app.controller("second", function($scope, sharedScope) {
$scope.data2 = sharedScope.data;
});
Yet another fun bit: In this case, you don't need to inject $scope or $rootScope. The following code works if you utilize Controller As.
Check the Fiddle
var app = angular.module("app", []);
app.factory("sharedScope", function() {
var _this = this;
_this.data = {text: "init text from factory"};
return _this;
});
app.controller("first", function(sharedScope) {
var _this = this;
_this.data1 = sharedScope.data;
});
app.controller("second", function(sharedScope) {
var _this = this;
_this.data2 = sharedScope.data;
});
For even more fun, consider controllers, services, and factories as classes.
More Fiddles
var app = angular.module("app", []);
var SharedScope = function(){
var _this = this;
_this.data = {text: "init text from factory"};
return _this;
};
app.factory("sharedScope", SharedScope);
var First = function(sharedScope){
var _this = this;
_this.data1 = sharedScope.data;
};
var Second = function(sharedScope){
var _this = this;
_this.data2 = sharedScope.data;
};
First.$inject = ['sharedScope'];
Second.$inject = ['sharedScope'];
app.controller("first", First);
app.controller("second", Second);
I've been playing at implementing Josh Carroll's Guidelines to Avoid "Scope Soup"
JavaScript passes objects by reference, so all scopes will point to the same object. Why not just do this?
app.factory("sharedData", function() {
return {data: "init text from factory"};
});
app.controller("first", function($scope, sharedData) {
$scope.sharedData = sharedData;
});
app.controller("second", function($scope, sharedData) {
$scope.sharedData = sharedData;
});
and in your view:
<p>{{sharedData.data}}</p>

Mock action invoked on controller initialization in AngularJS in test

I'm having angularjs controller that basically looks like below
app.controller('MyCtrl', function($scope, service) {
$scope.positions = service.loadPositions(); // this calls $http internally
$scope.save = function() {
...
};
// other $scope functions here
});
Now every time I write test for any of the methods in $scope I need to stub service.loadPositions() like below:
it(should 'save modified position', function($controller, service, $rootScope) {
spyOn(service, 'loadPositions').andReturn(fakeData);
var scope = $rootScope.$new();
$controller('MyCtrl', {$scope: scope});
// test stuff here
})
Is there any way to avoid this first stubbing in every test? I mean If I already tested that this action is invoked on controller start, I don't really need stubbing this in every next test. This introduces a lot of repetition in each test.
EDIT
I stubmbled upon ngInit and I thought I could use it but it seems not to be recommended way do to such things, but I'm not sure why?
Use a beforeEach inside your describe function:
describe('My test', function() {
var $controller,
$rootScope,
serviceMock;
beforeEach(function() {
serviceMock = { loadPositions: function() {} };
spyOn(serviceMock, 'loadPositions').andReturn(fakeData);
inject(_$controller_, _$rootScope_) {
$rootScope = _$rootScope_.$new();
$controller = _$controller_('MyCtrl',
{$scope: $rootScope, service: serviceMock});
};
});
it('should save modified position', function() {
// test stuff here
});
});
Notice that I have moved the controller initialization to beforeEach as well so you don't have to do it again in every test.
Just in case you're wondering what the underscores in the inject arguments are for, they enable the test to declare a local $controller variable and a local $rootScope variable. Angular just ignores them when it's resolving the function dependencies.
Update: There was a little bug in the example code. Just fixed it.
You can move this into beforeEach() and use $provide to always return your fake service.
Not knowing all of your test code something like this should work.
var scope, controller;
beforeEach(module("app", function($provide){
var mockedService = {
loadPositions: function(){return fakeData;}//or use sinon
};
$provide.value('service', mockedService);
});
beforeEach(inject(function($rootScope, $controller) {
scope = $rootScope.$new();
controller = $controller;
}));
it(should 'save modified position', function() {
controller('MyCtrl', {$scope: scope});
// test stuff here
});

Testing Angular Controllers defined like angular.module('myApp').controller(

I am playing around with https://github.com/angular/angular-seed
A controller is defined in app/controllers.js like this
'use strict';
function MyCtrl1() {}
MyCtrl1.$inject = [];
this doesn't pass jshint as MyCtrl1 is referenced in app/app.js and not in my globals list.
According to Brian Ford and others I have read the preferred style is
angular.module('myApp').controller('MyCtrl1', [], function () {});
I like this better as it's not in the global scope, but now my testacular specs fail because this doesn't work anymore:
var myCtrl1;
beforeEach(function(){
myCtrl1 = new MyCtrl1();
});
How do I get a reference to this controller which is defined in the "preferred" style for testing purposes?
credit due to both Javito and Xesued:
beforeEach(module('myApp'));
var scope, ctrl;
beforeEach(inject(function($controller, $rootScope) {
scope = $rootScope.$new();
ctrl = $controller('MyCtrl1', {$scope: scope});
}));
Try,
beforeEach(inject(function($controller) {
scope = {};
MyCtrl1 = $controller('MyCtrl1', {
$scope: scope
});
}));

Categories

Resources