Jasmine unit test for AngularJS controller - angularjs

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

Related

TypeError: undefined is not a constructor (evaluating 'angular.controller('myView')')

I'm getting the below error while doing karma/jasmine unit testing for both the test cases.I tried by modifying the controller by adding angular.controller in the spec file even then it is not working.Is there any way to fix?
TypeError: undefined is not a constructor (evaluating 'angular.controller('myView')')
myView.spec.js
// myView.spec.js
(function(){
describe('controller: myView', function(){
var module,myView,$q, $rootScope, $scope, uiGridConstants, overviewService, commonService, $timeout;
beforeEach(function() {
module = angular.module('app.myView');
controller= angular.controller('myView')
});
beforeEach(inject(function ($controller, _$q_, _$rootScope_, _$timeout_) {
$q= _$q_;
$rootScope = _$rootScope_;
$timeout= _$timeout_;
myView= $controller('myView', {
$q : _$q_,
$rootScope : _$rootScope_,
$timeout: _$timeout_
});
}));
describe("myViewto be defined", function() {
it("should be created successfully", function () {
expect(controller).toBeDefined();
});
it("overview should be defined", function () {
expect(myView()).toBeDefined();
});
});
});
})();
and myView.js
(function() {
'use strict';
angular
.module('app.myView')
.controller('myView', myView);
function myView($q, $rootScope, $scope, uiGridConstants, myViewService, commonService, $timeout) {
var vm = this;
vm.callFeedback = function () { };
})();
Sharing following code
// myView.spec.js
(function(){
describe('myView', function(){
var $controller, myView;
//we use angular-mocks to specify which modules we'll need within this
//test file.
beforeEach(angular.mock.module('app.myView'));
// Inject the $controller service to create instances of the controller
//(myView) we want to test
beforeEach(inject(function(_$controller_) {
$controller = _$controller_;
myView = $controller('myView', {});
}));
// Verify our controller exists
it('should be defined', function() {
expect(myView).toBeDefined();
});
});
})();
We set _$controller_to the $controller variable we created and then create an instance of our controller by calling $controller('myView', {}). The first argument is the name of the controller we want to test and the second argument is an object of the dependencies for our controller.
You should pass the injected parameters to your controller as shown:
(function() {
'use strict';
angular
.module('app.myView')
.controller($q,$rootScope,$scope,uiGridConstants,'myView', myView);
function myView($q, $rootScope, $scope, uiGridConstants, myViewService, commonService, $timeout) {
var vm = this;
vm.callFeedback = function () { };
})();
Also make sure that your module has all the necesary dependences in the angular.module('app.myView',['uiGridConstants', ...'etc']);

TypeError: $controller is not a function - angularjs with jasmine

I am trying to set up a test for AngularJS app using Jasmine. It follows the docs but is a bit simpler. The fiddle has the following code:
angular.module('myapp', [])
.controller('MyCtrl', ['$scope', function MyCtrl($scope) {
$scope.greeting = "hello";
}]);
describe('My controller', function() {
var $controller;
module('myapp');
inject(function(_$controller_) {
$controller = _$controller_;
});
it('greets', function() {
var $scope = {};
var controller = $controller('MyCtrl', {
$scope: $scope
});
expect($scope.greeting).toEqual('hello');
})
});
And Jasmine reports an error: TypeError: $controller is not a function.
How to correct the code to get rid of this error and be able to test the controller?
You need to instantiate the app module and inject $controller for each test using beforeEach blocks:
describe('My controller', function() {
var $controller;
beforeEach(module('myapp'));
beforeEach(inject(function(_$controller_) {
$controller = _$controller_;
}));
it('greets', function() {
var $scope = {};
var controller = $controller('MyCtrl', {
$scope: $scope
});
expect($scope.greeting).toEqual('hello');
})
});
Demo: https://jsfiddle.net/f5ebb55f/6/

Injecting custom factory in jasmine test

I tried to inject a factory to my controller in jasmine test like in
Unit testing AngularJS factories that have dependencies
When I $provide a factory in the test, I would expect the controller to use the provided factory. But the console.log still prints 'real value'. I don't get it.
var app = angular.module('mod', []);
app.factory('factoryA', [
function () {
return "real value";
}
]);
app.controller('myController', ['factoryA',
function (factoryA) {
console.log(factoryA);
}
]);
describe("test", function() {
var $scope, $controller, $httpBackend;
var app;
beforeEach(function() {
module(function($provide) {
$provide.factory('factoryA', function () { return "fake value"; });
});
app = module("mod");
inject(function (_$controller_, _$httpBackend_, $rootScope) {
$httpBackend = _$httpBackend_;
$scope = $rootScope.$new();
$controller = _$controller_;
});
});
it("works", function() {
$controller("myController", { '$scope': $scope });
});
});
You would need to get the $provider from the module mod so pass module name as first argument so that it overrides the factoryA definition that was created originally. Or load the module - module("mod") - before setting up mock.
module('mod', function($provide) {
$provide.factory('factoryA', function () { return "fake value"; });
});
Another way it to create mocks and pass it to the controller creation.
describe("test", function() {
var $scope, $controller, $httpBackend;
var app, factoryA;
beforeEach(function() {
module(mod);
inject(function (_$controller_, _$httpBackend_, $rootScope) {
//...Your code
//Mock factory
factoryA = jasmine.createSpy('factoryA');
factoryA.and.returnValue("fake value");
});
});
it("works", function() {
//Pass the mock factory
$controller("myController", { '$scope': $scope, factoryA:factoryA });
});
});

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.

AngularJS controller instantiation in unit tests - functions not binding to scope values

I have a service that synchronously returns data to a controller:
angular.module('app').controller(function($scope, myService) {
$scope.foo = myService.getFoo();
});
This works just fine in the browser. In my unit tests, $scope.foo is undefined:
beforeEach(function () {
module('app');
myService = jasmine.createSpyObj('myService', ['getFoo']);
inject(function($controller, $rootScope) {
$scope = $rootScope.$new();
ctrl = $controller('ModelSliderCtrl', {
myService: myService,
$scope: $scope
});
});
});
it('should have foo on the scope', function() {
myService.getFoo.and.returnValue({});
expect(myService.getFoo).toHaveBeenCalled(); // PASS
$scope.$digest();
expect($scope.foo).toBeDefined(); // FAIL - $scope.foo is undefined
});
This does work in both the browser and tests:
angular.module('app').controller(function($scope, myService) {
$scope.init = function() {
$scope.foo = myService.getFoo();
};
$scope.init();
});
.
it('should have foo on the scope', function() {
myService.getFoo.and.returnValue({});
$scope.init();
expect(myService.getFoo).toHaveBeenCalled(); // PASS
expect($scope.foo).toBeDefined(); // PASS
});
I'd like to believe I'm fairly well-versed in Angular, Jasmine and JavaScript. I've also asked some colleagues who are equally puzzled.
Can anyone explain to me what is going on here?
You are setting up a mock
it('should have foo on the scope', function() {
myService.getFoo.and.returnValue({});
after your controller has been instantiated. It's too late to set up the mock by then, do it before instantiating your controller since you are executing init() right away.
myService = jasmine.createSpyObj('myService', ['getFoo']);
myService.getFoo.and.returnValue({});
inject(function($controller, $rootScope) {

Resources