How to test an angular app with class inheritance? - angularjs

I can test normal controllers fine. I cannot test controllers that inherit a base controller.
This is how we've been defining subclassed-controllers:
app.NavController = app.BaseController.extend({
...
});
This is the base:
app.BaseController = Class.extend({
$scope: null,
init: function($scope) {
this.$scope = $scope;
this.defineListeners();
this.defineScope();
},
defineListeners: function() {
// this.$scope.$on('$destroy',this.destroy.bind(this));
},
...
});
app.controller('BaseController', ['$scope', function($scope) {
return new app.BaseController($scope);
}]);
However, running Karma produces:
TypeError: 'undefined' is not an object (evaluating 'app.BaseController.extend')
Is there a different way of doing this? I've removed IIFE wrappers for testing. Class.js is included in my Karmaconfig. I'm using John Resig's Class inheritance.

by adding this to instantiate the sub-classes prototype, we can get access to base class by simply injecting $controller as a dependency.
app.controller('SubController', ['$scope','$location','$controller',
function($scope, $location, $controller) {
var controller = {
...
controller.prototype = $controller('BaseController', {
$scope: $scope
});
controller.init($location);
return controller;

Related

Unknown Provider error creating angular.js service with require.js

I'm trying to get a service working using AMD loading with require.js. I have my controllers all working and can load $rootScope to store app-wide variables/data but I would also like to add some methods to modify or refresh some of the app-wide data when needed. Thus I was trying to create a service object in require.js
So I create this sort of a thing:
MyService.js
define( ["angular"], function (angular) {
var MyService = function($scope, $rootScope) {
this.$scope = $scope;
this.$rootScope = $rootScope
}
MyService.$inject = ["$scope"];
MyService.prototype = {
constructor: MyService,
// comma separated list of methods and properties
}
return MyService
})
I similarly define a couple of controllers (all already working fine) then access them in my require.js config something like:
requirejs(
[ "angular", "MyService", "MyController" ],
function(angular, MyService, MyController) {
let myAppHandle = angular.module('myApp',[])
.service("myService", ['$scope','$rootScope', MyService])
// the following throws an error:
// angular.js:15697 Error: [$injector:unpr]
// Unknown provider: $scopeProvider <- $scope <- myService
myAppHandle.controller('MyController', ['$scope', 'myService', MyController])
}
)
Any help is appreciated
Ahh, I was guessing in the dark at how to create a service properly in require.js and after some tinkering and banging my face on the keyboard, I eventually figured out the problem was not with the controller getting the service, but with the service getting $scope
The examples I found for require.js dealt mostly with controllers, components and directives and and in just about every case, $scope was the first thing passed in.
I took $scope out of the .service() in the definition and in the constructor for the class and it's now working.
define( ["angular"], function (angular) {
var MyService = function($rootScope) {
// using $rootScope to store app-wide variables
// accessible directly or via this service
// potentially updated/populated by methods in this service
this.$rootScope = $rootScope
}
MyService.prototype = {
constructor: MyService,
// comma separated list of methods and properties
}
return MyService
})
and
requirejs(
[ "angular", "MyService", "MyController" ],
function(angular, MyService, MyController) {
let myAppHandle = angular.module('myApp',[])
.service("myService", ['$rootScope', MyService])
myAppHandle.controller('MyController', ['$scope', 'myService', MyController])
}
)
for reference, this is what the controller looks like:
define( ["angular"], function (angular) {
var MyController = function($scope, myService) {
this.$scope = $scope
// make service handle available as class property
this.myService = myService
}
MyController.$inject = ["$scope"];
MyController.prototype = {
constructor: MyController,
// comma separated list of methods and properties
}
return MyController
})
(Hopefully this will save someone else the 2-3 hours I took figuring this out)

How do I know my controller was called, when unit testing with sinon?

Pretty new to Unit Testing in Angular. I've read loads about spies, stubs, and mocks but I am having a ton of trouble with execution of the basics:
Is my controller properly receiving the services that I have passed to the constructor?
Is a call to initializePage being made on instantiation?
Controller.spec (pretty sure the following is required)
'use strict';
describe('Controller: MainController', function() {
// load the controller's module
beforeEach(module('myApp'));
var MainController, scope;
// Initialize the controller and a mock scope
beforeEach(inject(function($controller, $rootScope) {
scope = $rootScope.$new();
MainController = $controller('MainController', { $scope: scope });
}));
Rest of spec:
it('should have called initializePage', function() {
var spyInstance = sinon.spy(MainController, "initializePage");
assert(spyInstance.called, "initializePage() was not called once");
});
});
I have been thinking that a spy is sufficient for this, but I'm not sure if MainController is even being executed. Currently spyInstance throws false. Do I need a stub here instead? Why?
Controller
class MainController {
constructor($scope, $http, $state, Session) {
this.$scope = $scope;
this.$state = $state;
this.Session = Session;
this.initializePage();
}
initializePage() {
//blah blah
}
Thanks.
Revision:
main.controller.spec.js
describe('Controller: MainController', function() {
// load the controller's module
beforeEach(module('scriybApp'));
var mainControllerInstance, scope;
// Initialize the controller and a mock scope
beforeEach(inject(function($controller, $rootScope) {
scope = $rootScope.$new();
mainControllerInstance = $controller('MainController', { $scope: scope });
}));
it('should test the controller is in order', function() {
assert.isFunction(mainControllerInstance.$onInit, "$onInit() has not been defined");
sinon.spy(mainControllerInstance, "$onInit");
assert(mainControllerInstance.$onInit.called, "$onInit() called = false");
});
});
Controller testing has some pitfalls in Angular. Primarily because it is not possible to spy on a constructor of a class that is available only as class instance (which the result of $controller(...) is). When $controller(...) is called, there's nothing to spy on, the constructor has been already called, end of the story.
For this purpose ES6/CommonJS modules should be utilized in addition to Angular modules to expose controller class and spy on prototype methods. Since ES6 is already used in project, it is
export class MainController { ... }
and
import { MainController } from '...';
...
scope = $rootScope.$new();
sinon.spy(MainController.prototype, 'initializePage');
mainControllerInstance = $controller('MainController', { $scope: scope });
assert(MainController.prototype.initializePage.called);
assert.strictEqual(mainControllerInstance.$scope, $scope);
...
But more importantly, initializePage reinvents the wheel. Its job is already handled by $onInit lifecycle hook in Angular 1.5 and higher. It is called on directive compilation automatically and can serve as a substitution to pre-link function.
$onInit isn't called on controller instantiation but it can be safely assumed that it will be in directive, thus there's no need to put spy on it. It is more test-friendly, the test becomes
class MainController {
constructor($scope, $http, $state, Session) {
this.$scope = $scope;
this.$state = $state;
this.Session = Session;
}
$onInit() {
//blah blah
}
}
and
scope = $rootScope.$new();
mainControllerInstance = $controller('MainController', { $scope: scope });
assert.isFunction(mainControllerInstance.$onInit);
assert.strictEqual(mainControllerInstance.$scope, $scope);
...

Mocking a service dependency in an Angular controller with jasmine

I've been trying to get started with unit testing in angular with karma and jasmine, and i've been pulling my hair out trying to wrap my head around how to test controllers with dependencies. I tried mocking a spy with a jasmine spyObj and registering it in the beforeEach hook, but for some reason the spy isn't being recognized.
Here's the code:
angular.module('testModule', [])
.controller('TestController', [
'$scope',
'TestService',
function ($scope, TestService) {
$scope.data = TestService.load();
}])
.factory('TestService', function () {
return {
load: function(){
return "foo";
}
}
});
and here's the test
describe('TestController', function() {
var $controller, $scope, TestService;
beforeEach(module('testModule'), function($provide){
TestService = jasmine.createSpyObj("TestService", ["load"]);
TestService.load.andReturn("bar");
$provide.value("TestService", TestService)
});
beforeEach(inject(function(_$controller_, $rootScope, _TestService_) {
$scope = $rootScope.$new();
TestService = _TestService_;
$controller = _$controller_('TestController', {
$scope: $scope,
TestService: TestService
});
}));
it('should set $scope.data to bar when TestService.load is called', function() {
expect(TestService.load).toHaveBeenCalled();
expect($scope.data).toEqual("bar");
}); });
Both assertions in the test fail.
I get 'Error: Expected a spy, but got Function' when i call expect(TestService.load).toHaveBeenCalled();
and if I call expect($scope.data).toEqual("bar"), I get Expected 'foo' to equal 'bar'. "Foo" is coming from the actual service, not the spy object.
Thanks for your help.
Instead of jasmine.createSpyObj, it will be easier to use the existing service that the $injector provides and then just mock the single method. You can achieve this with spyOn instead:
describe('TestController', function() {
var $controller, $scope, TestService;
beforeEach(module('testModule'));
beforeEach(inject(function(_$controller_, $rootScope, _TestService_) {
$scope = $rootScope.$new();
TestService = _TestService_;
spyOn(TestService, 'load').and.returnValue('bar');
$controller = _$controller_('TestController', {
$scope: $scope,
TestService: TestService
});
}));
it('should set $scope.data to bar when TestService.load is called', function() {
expect(TestService.load).toHaveBeenCalled();
expect($scope.data).toEqual("bar");
});
});
In your beforeEach you are injecting in _TestService_ and then overwriting the one you declared in the previous beforeEach via:
TestService = _TestService_;
Remove that code and your test should succeed.
Also there is no need to do this:
$provide.value("TestService", TestService)
Basically you're trying to use Angular's dependency injection when you're manually injecting things which is unnecessary.

Jasmine unit test for AngularJS controller

I'm trying to unit test a controller in AngularJS using Jasmine. The controller has slightly different syntax than most documentation I can find and I'm getting this error...
http://errors.angularjs.org/1.2.15/$injector/unpr?p0=propertiesProvider%20%3C-%20properties
The controller...
myApp.controller('myController', function($scope, $rootScope, param) {
param.info.get().then(function(example){
//...
});
});
The test js...
describe('myApp', function() {
var $scope, $rootScope, param, createController, properties;
beforeEach(module('myApp'));
debugger; //runs
beforeEach(inject(function($injector) {
debugger; //doesn't run
$rootScope = $injector.get('$rootScope');
properties = $injector.get('properties');
param = $injector.get('param');
$scope = $rootScope.$new();
var $controller = $injector.get('$controller');
createController = function() {
return $controller('myController', {
'$scope': $scope,
'$rootScope':$rootScope,
'properties':properties,
'param':param,
});
};
}));
var a;
it('should pass this basic test no matter what', function() {
a = true;
expect(a).toBe(true);
});
});
Param definition...
myApp.factory("param", function($http) {
return {
info: {
get: function() {
return $http.get(myApp.url("/param/info")).then(function(response) {
return response.data.data;
});
}
}
}
myApp.run...
myApp.run(['$http', '$rootScope', 'properties', function($http, $rootScope, properties){
...
}]);
If you look at the error carefully it says error in propertiesProvider, you are injecting properties in your test , and hence it's looking for propertiesProvider which doesn't exist . so it throws error.
If properties is an angular Service and you are injecting that in your controller , while testing you need not inject that service again to your test, angular mock takes care of that .
I'll suggest you use npm package generator-yosapy to bootstrap your controller test

Unit testing socket.io with karma and jasmine

The project I am working on makes use of socket.io for some of the components of the UI.
I am trying to write unit tests for this particular section of the application. I am using: angular-socket-io and angular-socket.io-mock to mock the server side component.
I am using everything at the simplest level, so I have my factory:
myapp
.factory('notify', function (socketFactory) {
return socketFactory();
});
This is the controller
myapp
.controller('NotificationsCtrl', function ($scope, notify) {
$scope.items = []
notify.emit("loadItems",{},function(res){
$scope.items = res.res
});
});
and finally the unit test:
describe('Controller: NotificationsCtrl', function () {
// load the controller's module
beforeEach(module('app'));
var NotificationsCtrl,
scope;
// Initialize the controller and a mock scope
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
NotificationsCtrl = $controller('NotificationsCtrl', {
$scope: scope
});
}));
it('The scope.items should change somehow', function() {
expect(scope.items.length).toEqual(3);
});
});
I cannot realize what is missing to make this working.
How should I change my code to make it happen?
Thanks

Resources