How can'I test angularJs event '$on'? - angularjs

I'am using angularJs and I want to test $on event in my controller so I try to write test case in this way :
beforeEach(inject(function ($controller,$injector) {
$rootScope = $injector.get('$rootScope');
$scope = $rootScope.$new();
controller = $controller('MyController', {
$scope: $scope
});
}));
spyOn($rootScope, '$broadcast').and.callThrough();
spyOn($rootScope, '$on').and.callThrough();
it('should set $rootScope.settings.layout.pageBodySolid to false', function() {
controller = CreateTarget();
expect($rootScope.$broadcast).toHaveBeenCalled();
expect($rootScope.settings.layout.pageBodySolid).toBe(false);
});
But I get this error :
Error: spyOn could not find an object to spy upon for $broadcast()
I also try to do test in this way :
beforeEach(inject(function ( $controller,$injector) {
$rootScope = $injector.get('$rootScope');
$scope = $rootScope.$new();
controller = $controller('MyController', {
$scope: $scope
});
CreateTarget = function() {
$controller('MyController', {$scope: $scope});
}
}));
it('should set $rootScope.settings.layout.pageBodySolid to false', function() {
controller = CreateTarget();
$rootScope.$broadcast('$viewContent');
expect($rootScope.settings.layout.pageBodySolid).toBe(false);
});
This time I get this error
TypeError: Cannot read property 'layout' of undefined
And this is my controller event
$scope.$on('$viewContent', function () {
$rootScope.settings.layout.pageBodySolid = false;
$rootScope.settings.layout.pageSidebarClosed = false;
});
I don't know how can I do this. It's really my first experience with angularJs test I need your help.Thanks in advance.

There's a few issues here:
First, the spyOn. The $scope you pass into your controller is actually a new instance, yet you're spying on the $rootScope object, so they're two totally different instances.
Second, you don't necessarily want to test that $broadcast or $on are called because then you are essentially testing Angular. What you want to test in those cases is your own code and the outcome of the event broadcast.
So, in your case, what you want to test is that 'when the $viewContent event is received, those two properties on $rootScope should be set to false.
Which brings me to my third point: the layout undefined message is because you're trying to set a value on the settings property of $rootScope, but you never initialize that to anything. At the point you're setting it, it is undefined.
TL;DR, this is how you could test this (note: this code may need tweaked. I'm typing this by memory and not actually testing that it works. This is just to give you the idea)
beforeEach(inject(function ($controller, $injector, _$rootScope_) {
$rootScope = _$rootScope;
$scope = $rootScope.$new();
$rootScope.settings = {
layout : {
pageBodySolid : true,
pageSidebarClosed : true
}
};
controller = $controller('MyController', {
$scope: $scope
});
}));
it('should set $rootScope.settings.layout properties to false', function() {
$rootScope.$broadcast('$viewContent');
// Run a digest to notify watchers
$rootScope.$digest();
expect($rootScope.settings.layout.pageBodySolid).toBe(false);
expect($rootScope.settings.layout.pageSidebarClosed).toBe(false);
});

Related

How to trigger $onInit or $onChanges implictly in unit testing Angular component controller?

I'm using Angular 1.5.5 and Jasmine as test framework. Currently I have to do something like this so that the test passes:
function createController(bindings) {
return $componentController('myController', null, bindings);
}
beforeEach(inject(function (_$componentController_) {
$componentController = _$componentController_;
}));
describe('on pages updated', function () {
beforeEach(function () {
controller = createController({prop1: 0, prop2: 0});
controller.$onInit(); // you see I have to explitcitly call this $onInit function
});
it('should update isSelected and currentPage', function () {
expect(controller.prop1).toBe(0);
expect(controller.prop2).toBe(0);
controller.prop1= 1;
controller.prop2= 2;
controller.$onChanges(controller); // and $onChanges here as well
expect(controller.prop1).toBe(1);
expect(controller.prop2).toBe(2);
});
});
There is an issue in github regarding this: https://github.com/angular/angular.js/issues/14129
Basically it is working as intended, not calling $onInit or $onChanges automatically.
it makes no sense (or low sense) to execute $onInit, I explain it: $componentController is to instance controllers a kind of replacement for $controller, but instead of creating instances of controllers registered by the controllerProvider it creates instances of controllers registered through directives (the ones that satisfies a component definition). So, once you have the instance of the controller, you can call manually $onInit, and all the lifecycle of your controller, the idea is that you are testing a controller, not a directive (and its relationships).
I don't know if this will help, but for testing components I do the following
beforeEach(module('templates'));
var element;
var scope;
beforeEach(inject(function ($rootScope, $compile) {
scope = $rootScope.$new();
scope.draw = new ol.source.Vector({ wrapX: false });
element = angular.element('<custom-element draw="draw"></custom-element>');
element = $compile(element)(scope);
}));
var controller;
beforeEach(inject(function ($rootScope, $componentController) {
scope = $rootScope.$new();
scope.draw = new ol.source.Vector({ wrapX: false });
controller = $componentController('customElement', {draw: new ol.source.Vector({ wrapX: false })}, { $scope: scope });
}));
and $onInit() and $onChanges() get triggered when they should be, by themselves
You would require to take reference of controller from compiled version of element. As shown below:
describe('Component: Test Method', function () {
beforeEach(inject(function (_$rootScope_) {
scope = _$rootScope_.$new();
}));
it('should set value of selectedPackage to null after $onChanges event', inject(function ($compile) {
// Trigger 1st $onChanges
scope.selectedPackage = 'packageCode';
var element = angular.element('<select-my-tv selected-package="selectedPackage"></select-my-tv>');
element = $compile(element)(scope);
scope.$digest();
//Extract the Controller reference from compiled element
var elementController = element.isolateScope().$ctrl;
// Assert
expect(elementController.selectedPackage).toBeNull();
}));
});
});

Error: $injector:unpr AngularJS Testing mocking object instances

I keep running into problems at the minute when i'm testing my AngularJS applications, I try and inject all dependencies however it doesn't seem to be working, any help is greatly appreciated :)
It's quite a large application and i'm trying to break things down as much as possible and test them, however we have a factory called firebaseUser which is, as you can guess a firebaseUser. We also have an instance of this known as userInstance so I'm getting errors whenever I try and mock userInstance.
describe('Dashboard Start Controller', function () {
var scope, ctrl;
beforeEach(function () {
MockFirebase.override();
module('noodleApp.start');
module('noodleApp.noodleFactory');
module('noodleApp.firebaseUser');
module('noodleApp.start');
});
beforeEach(inject(function($rootScope, $controller, $injector) {
scope = $rootScope.$new();
ctrl = $controller('StartController', {$scope: scope});
}));
afterEach(function () {
scope.$destroy();
});
it('should be available', function() {
expect(ctrl).toBeDefined();
});
it('should init with filter being set to all', function() {
expect(scope.filterOn).toBe('all');
});
});
Whenever I run this test I get the following error: Unknown provider: userInstanceProvider <- userInstance <- StartController
$controller is a call to $inject but treated as a service. What happens is you are instantiating the controller but because $inject is used, and you are not passing userInstance, it looks for a provider which isn't found. You need to make sure to pass the service/factory/resolve to your $controller method. By mocking it out and passing it to the controller, you can isolate what you expect to happen and only test the controller in this unit test.
beforeEach(inject(function($rootScope, $controller) {
scope = $rootScope.$new();
userInstanceMock = {
// mock out all methods here
foo: sinon.stub()
}
ctrl = $controller('StartController', {
$scope: scope,
userInstace: userInstaceMock
});
}));

How do I spy on a controller method using Jasmine?

I'm using the "controller as" syntax to create my controller. I have a private initialization function that calls a function to load the default data.
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
var mc = this;
mc.dataLoaded = false;
function init() {
mc.loadData();
}
mc.loadData = function(){
mc.dataLoaded = true;
}
init();
});
In my test I'm creating a spy to check whether the loadData function has been called. Although I can verify that the function has been called by testing for the mc.dataLoaded flag, my spy doesn't seem to record the function being called. How can I get the spy to correctly record the function call?
describe('Testing a Hello World controller', function() {
var $scope = null;
var ctrl = null;
//you need to indicate your module in a test
beforeEach(module('plunker'));
beforeEach(inject(function($rootScope, $controller) {
$scope = $rootScope.$new();
ctrl = $controller('MainCtrl as mc', {
$scope: $scope
});
spyOn($scope.mc, 'loadData').and.callThrough();
}));
it('should call load data', function() {
expect($scope.mc.loadData).toHaveBeenCalled();
//expect($scope.mc.dataLoaded).toEqual(false);
});
});
Plunker link
This sequence of lines:
ctrl = $controller('MainCtrl as mc', {
$scope: $scope
});
spyOn($scope.mc, 'loadData').and.callThrough();
Means that the Jasmine spy is created after the controller has already been instantiated by $controller. Before the spy is created, the init function has already executed.
You can't switch the lines around either, because MainCtrl needs to exist before you can spy on a method on it.
If the init function calls another service, then spy on that service's method and assert that the service is called correctly. If MainCtrl is just doing something internally, then test the result of that, for example, by asserting that controller's data/properties are updated. It may not even be worth testing if it's trivial enough.
Also, since you're using the controller as syntax, you can reference the controller through the return value of calling $controller, rather than accessing the scope directly:
ctrl = $controller('MainCtrl as mc', {
$scope: $scope
});
ctrl.loadData === $scope.mc.loadData; // true
I found a solution that allowed me to avoid changing my controller. I included a $state mock service in the test suite's beforeEach method, and gave it a reload mock method:
beforeEach(inject(function ($controller, $rootScope) {
stateMock = {
reload: function() {
myCtrl = $controller('MyCtrl');
}
};
...
Then within the jasmine tests, I can simply call stateMock.reload() to re-initialize my controller while preserving my spies I declared in another beforeEach block.

AngularJs: Test service with Jasmine

I did this controller
app.controller('controller',['$scope','httpServices',function($scope,httpServices){
$scope.items= undefined;
httpServices.getItems( function(items){
$scope.items= items;
});
}]);
and I wrote this test
describe('controller', function () {
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
controller = $controller('controller', {
'$scope': scope
});
}));
it('defined', function () {
expect(scope.items).toBeUndefined();
})
});
How I can test the scope.items after to have called the service?
I assume that your service httpServices is making some http requests. Therefore you should use the mock-backend service in order to test your controller.
Something like this, pay attention to the comments that I've made inside the code:
describe('Your specs', function() {
var $scope,
$controller,
$httpBackend;
// Load the services's module
beforeEach(module('yourApp'));
beforeEach(inject(function(_$controller_, $rootScope, _$httpBackend_) {
$scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
$controller = _$controller_;
//THIS LINE IS VERY IMPORTANT, HERE YOU HAVE TO MOCK THE RESPONSE FROM THE BACKEND
$httpBackend.when('GET', 'http://WHATEVER.COM/API/SOMETHING/').respond({});
var createController = function(){
$controller('controller', {$scope: $scope});
}
}));
describe('Your controller', function() {
it('items should be undefined', function() {
createController();
expect(scope.items).toBeUndefined();
});
it('items should exist after getting the response from the server', function () {
//THIS LINE IS ALSO VERY IMPORTANT, IT EMULATES THE RESPONSE FROM THE SERVER
$httpBackend.flush();
expect(scope.items).toBeDefined();
});
});
});
The question title states this is to test a service, but the code of the question looks like an attempt is being made to test the controller. This answer describes how to test the controller.
If you're testing the controller that calls httpServices.getItems, then you need to mock it/stub getItems in order to
Control it on the test
Not assume any behaviour of the real httpServices.getItems. After all, you're testing the controller, and not the service.
A way to do this is in a beforeEach block (called before the controller is created) provide a fake implementation of getItems that just saves the callback passed to it.
var callback;
beforeEach(inject(function(httpServices) {
callback = null;
spyOn(httpServices, 'getItems').and.callFake(function(_callback_) {
callback = _callback_;
});
});
In the test you can then call this callback, passing in some fake data, and test that this has been set properly on the scope.
it('saves the items passed to the callback on the scope', function () {
var testItems = {};
callback(testItems);
expect($scope.items).toBe(testItems);
});
This can be seen working at http://plnkr.co/edit/Z7N6pZjCS9ojs9PZFD04?p=preview
If you do want to test httpServices.getItems itself, then separate tests are the place for that. Assuming getItems calls $http, then you are most likely to need to use $httpBackend to handle mock responses. Most likely, these tests would not instantiate any controller, and I suspect not need to do anything on any scope.

Trigger a function in a controller with Karma

In an angular controller I have a function on my scope that looks like this:
$scope.myFunction = function(val) {
$scope.myVar = val;
$scope.mySettings.myPath = $scope.myBase + $scope.myVar;
}
In my karma unit test I want to trigger that function on the controller using a value i supply ... then check $scope.mySettings.myPath to make sure it is what i think it should be.
Im trying this in karma:
describe("Unit Testing: Controller Testing" , function() {
describe('myController' , function () {
var scope,ctrl;
beforeEach(module('myApp.controllers'));
beforeEach(inject(function($rootScope, $controller) {
scope = $rootScope.$new();
ctrl = $controller('myController', {
$scope: scope
});
}));
it('should have a working changeVideo function' , function(){
scope.changeVideo(scope.myVar[6]);
expect(scope.mySettings.myPath).to.equal('Expected String');
});
});
});
The error i see is that it cannot call equal method of undefined. Which is odd because scope.mySettings.myPath is actually predefined in the controller so it should never be undefined.
To give some background I do have access to the controller and the scope from karma. This one is stumping me though.
The solution to this was using toEqual() instead of to.equal()

Resources