Angular controller not initialized in jasmine test - javascript

I ran into issues writing jasmine tests for an AngularJS application using angular ui-router. My services and app get initialized properly in the test, but the controllers do not start up properly. I've taken the application in question out of the equation and reduced the problem to a simple one controller example that exhibits the same behavior. Here's the actual test code:
describe('Test', function() {
var async = new AsyncSpec(this);
var scope = {};
beforeEach(angular.mock.module('TestApp'));
beforeEach(angular.mock.inject(function($rootScope, $state, $templateCache) {
scope.$rootScope = $rootScope;
scope.$state = $state;
$templateCache.put('start.html', '<div class="start"></div>');
}));
async.it('Test that TestCtrl is initialized', function(done) {
scope.$rootScope.status = { done: false };
scope.$rootScope.$on('$stateChangeSuccess', function(event, state, params) {
expect(scope.$rootScope.status.done).toBe(true);
done();
});
scope.$state.transitionTo('start', {}, { notify: true });
scope.$rootScope.$apply();
});
});
Here's the complete runnable test
The application gets initialized correctly, the ui router is able to transition the application to the correct state, but the controller does not get initialized. I need the router to initialize the controllers as the router passes critical configuration to them. I want to avoid duplicating that configuration in the tests.
I must be missing something, but what? I appreciate any and all input, thanks!

You need to use the $controller service to instantiate your controller in your tests and pass it your scope. For example...
ctrl = $controller('TestCtrl', {$scope: scope});
Notice that I also moved the declaration of $rootScope.done to the TestCtrl to prevent an error about $rootScope.done being undefined. Here's the fiddle...
http://jsfiddle.net/C8QtB/3/

Related

How to Test $scope.$on in an AngularJS 1.5+ Component Using Jasmine

I'm trying to test that some code gets executed using $scope.$on in an AngularJS 1.5+ component. I'm not sure how to set up the $rootScope correctly in Jasmine so I can execute the broadcast. I'm using this stackoverflow page and this blog as a reference. Here is my code.
// Component
(function (app) {
app.component('demoComponent', {
controller: ['$scope' function ($scope) {
$scope.$on('someBroadcast', function (data) {
// do something with data...
});
}]
});
})(angular.module('demoApp'));
// Jasmine setup
var ctrl, $rootScope, $componentController;
beforeEach(function () {
module('demoApp');
inject(function ($rootScope, _$componentController_) {
ctrl = $rootScope.$new();
$componentController = _$componentController_('demoComponent', { $scope: ctrl }, null);
});
});
My code breaks down in the inject function in the Jasmine setup. Does anyone know what I need to change to get this working?
Since ctrl is the scope that you will be using inside of your controller, you can do this:
ctrl.$broadcast('someBroadcast', 'test', 'values, 123);
And as a side note, usually you will name your scopes something like scope or $scope, or maybe event controllerScope. ctrl implies that the object is a controller, which it is not.

How to load actual dependencies into a Angularjs test with Jasmine and Karma?

I have been looking around online, but seems, no one really injecting the actual dependencies into a unit test for Angularjs using Jasmine and Karma.
I think there is definitely a separation of concern for the testing process, but I also would like to know how it integrated well with current dependencies in use... so just in case a dependencies is not working well with my component, I will be aware of it!
So, I wonder how can I inject the actual dependencies? So far I found online articles are all about mocking it with a fake one... But I want to use the actual one. Right now, when I enter karma start I am getting a error of Error: [$injector:unpr] Unknown provider: _Provider <- _ <-MyService
I inject services in forEach block like this
beforeEach(angular.mock.inject(function(_MyService_) {
I wonder if its because I am not using the fake service?
describe('MyCtrl', function() {
//Data Exposure Prep
var $controller;
var $rootScope;
var $scope;
var controller;
var MyService;
dd1 = {
itinerary: globalMockData.d1,//I stored globalMockData somewhere else
};
beforeEach(angular.mock.module('myapp'));
beforeEach(angular.mock.inject(function(_$rootScope_, _$controller_, _$httpBackend_) {
$rootScope = _$rootScope_;
$controller = _$controller_;
$httpBackend = _$httpBackend_;
$scope = $rootScope.$new();
controller = $controller('MyCtrl', { $scope: $scope }, dd1);
}));
//BASIC INFO
describe('should receive sth', function() {
it('finds sth', function() {
expect(controller.answer).toBeDefined();
});
});
});
It really depend on your Controlller implementation.
If you're using scope, you'll probably want to test things on controller's scope, not on the controller itself.
Testing controller with $scope
function MyController($scope, MyService) {
$scope.greetUpperCase = function(name) {
return MyService.greet(name).toUpperCase();
}
}
Basically, when you’re working with $scope, you don’t really care about the controller itself, since it’s doing everything on it’s scope.
So, we will need to:
inject $rootScope
create a new scope
inject it to a new controller
test the $scope
it('test greetUpperCase()', function() {
var myScope = $rootScope.$new();
$controller('MyController', {
$scope: myScope
});
expect(myScope .greetUpperCase('bob')).toBe('HELLO BOB');
});
https://jsfiddle.net/ronapelbaum/pkhaxmdg/

Angular unit testing initialize "nameless" controller in directive

I'm curious as to how I would go about unit testing what I think is an anonymous controller inside of a directive.
directive.js
app.directive('directive',
function() {
var controller = ['$scope', function($scope) {
$scope.add = function() { ... };
}];
return {
restrict: 'A',
scope: {
args: '='
},
templateUrl: '...',
controller: controller
};
}
};
Is a controller defined as such able to be unit tested? I have tried to initialize it several different ways. Currently I have it setup like this:
describe('The directive', function() {
var element,
scope,
controller;
var args = {
...
}
beforeEach(module('app'));
beforeEach(module('path/to/template.html'));
beforeEach(function() {
inject(function($compile, $rootScope, $controller) {
scope = $rootScope.$new();
scope.args = args;
element = angular.element('<div directive></div>');
template = $compile(element)(scope);
scope.$digest();
controller = element.$controller;
});
});
// assertions go here
});
I keep getting TypeError: 'undefined' is not an object (evaluating ...) errors, so I don't think I am initializing the controller correctly. I mainly want to know if something like this is unit testable without changing the directive's source code at all.
I'm not sure if what you are trying to do is possible. However, I do know that there is a much easier way and that is to make it a standard controller. (You seem to be aware of this already but it's worth pointing out.)
The logic in a controller really shouldn't be dependent on the directive anyway so by making a named controller you are separating concerns which is a good thing. You can even see this used in recommended style guides for AngularJS. Once you have the controller set up properly you shouldn't have any issues testing it. Splitting it out like that also helps in doing proper dependency injection making for simpler code and simpler tests.

How do you inject a mocked angular service that is used in the app.run function?

I have added some logic for authentication in my angular app and the initial the call to the service wrapping the webapi is executed in the app.run function, like this:
myApp.run(function ($rootScope, $location, myService) {
myService.getCurrentUser().then(function (promise) {
//reroute to either access denied or the start page when access is verified.
});
// register listener to watch route changes
$rootScope.$on("$routeChangeStart", function (event, next, current) {
//check access
});
});
After this all my unit tests broke, since I don't know how to inject the mocked version of myService before creating the app module. I've factored out the service and the mocked service in a seperate module so I can create it before creating the actual app. Like this:
angular.mock.module('ServiceModule');
angular.mock.module('EArkivApp', function ($provide, myMockedService) {
$provide.value('myService', myMockedService);
});
This is not working however, it complains that "myMockedService" (which is part of the ServiceModule) is an unkown provider. Do you have any good suggestions of how I should solve this?
Could you provide us for a jsfiddle?
Meanwhile, if you are using Karma then you can use something like this:
/*global describe, beforeEach, module, inject, it, expect*/
describe('Controller: MyCtrl', function () {
'use strict';
// load the controller's module
beforeEach(module('myApp'));
var MyCtrl,
scope,
modalInstance;
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope, myMockedService) {
scope = $rootScope.$new();
modalInstance = _$modal_.open({
templateUrl: 'views/my-template.html'
});
MyCtrl = $controller('MyCtrl', {
$scope: scope,
$myMockedService: myMockedService
});
}));
it('should be true', function () {
expect(true).toBe(true);
});
});
I learned this by using Yeoman scaffolding, by the way :)

Angular Karma Test getting "TypeError: 'undefined' is not a function" on a $scope.$parent function

While running grunt test on a Yeoman (1.0RC1) scaffolded Angular (1.0.7) app, I'm getting the following error:
TypeError: 'undefined' is not a function (evaluating '$scope.$parent.userLoggedIn(true)')
userLoggedIn() is within a parent controller index.js. The function itself runs fine within the angular app.
This error doesn't occur on my other $scope.$parent boolean or strings variables in the controller, so it's related directly to calling functions within a parent.
I'm thinking that I'm either using $scope.$parent the wrong way or that I need to define my index.js controller in the test, but Karma testing documentation is sporadic, so it's hard to know.
EDIT: Here is the controller:
'use strict';
angular.module('uiApp')
.controller('LookupCtrl', ['$scope', function ($scope) {
$scope.awesomeThings = [
'HTML5 Boilerplate',
'AngularJS',
'Karma'
];
$scope.$parent.userLoggedIn(true);
}]);
Here is the test:
'use strict';
describe('Controller: LookupCtrl', function () {
// load the controller's module
beforeEach(module('uiApp'));
var LookupCtrl,
scope;
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
LookupCtrl = $controller('LookupCtrl', {
$scope: scope
});
}));
it('should attach a list of awesomeThings to the scope', function () {
expect(scope.awesomeThings.length).toBe(3);
});
});
(Yes I know I'm just using the default awesomeThings test for now. I'm new to Angular testing).
You're giving the controller a rootScope, which doesn't have $parent (it's the root).
Change your controller code to call it correctly (using the prototype chain) and you'll be fine by just passing {my: props} as an object.

Categories

Resources