I need to test a controller with a lot of injections (services, factories, values), now I have a problem with a value...
I have the following situation:
This is my controller.
(function () {
'use strict';
angular
.module('otpConfigureDatasets')
.controller('otpConfigureDatasetsController', otpConfigureDatasetsController);
otpConfigureDatasetsController.$inject = ['$location', '$state', 'otpWebMapApp', 'otpWMDeltaTracker', 'otpWMStateCache', '$scope', '$timeout', 'otpConfigureDatasetService', 'otpAllDatasetsService', 'otpReviewListDatasetsService','otpConfigureDatasetSelectedTab'];
function otpConfigureDatasetsController($location, $state, otpWebMapApp, otpWMDeltaTracker, otpWMStateCache, $scope, $timeout, otpConfigureDatasetService, otpAllDatasetsService, otpReviewListDatasetsService, otpConfigureDatasetSelectedTab) {
var vm = this;
...
vm.tabs = [{
title: 'MY REVIEW LIST',
selectedAmt: vm.reviewData,
contentTemplate: './app/features/configureDatasets/reviewListDatasets.html',
active: otpConfigureDatasetSelectedTab.tab == 'MyReviewList'
}, {
title: 'ALL DATASETS',
selectedAmt: vm.allData,
contentTemplate: './app/features/configureDatasets/allDatasets.html',
active: otpConfigureDatasetSelectedTab.tab == 'AllDatasets'
}];
...
}
})();
My Spec.js below
'use strict';
describe('Test', function () {
var MainCtrl, scope, _otpWebMapApp_, _otpWMDeltaTracker_, _otpWMStateCache_, _otpConfigureDatasetService_,
_otpAllDatasetsService_, _otpReviewListDatasetsService_, _otpConfigureDatasetSelectedTab_;
beforeEach(function () {
module('otpConfigureDatasets');
});
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
MainCtrl = $controller('otpConfigureDatasetsController', {
$scope: scope, otpWebMapApp: _otpWebMapApp_, otpWMDeltaTracker: _otpWMDeltaTracker_, otpWMStateCache: _otpWMStateCache_,
otpConfigureDatasetService: _otpConfigureDatasetService_, otpAllDatasetsService: _otpAllDatasetsService_,
otpReviewListDatasetsService: _otpReviewListDatasetsService_, otpConfigureDatasetSelectedTab: _otpConfigureDatasetSelectedTab_
});
}));
it('Should be true', function () {
expect(true).toBe(true);
});
});
But when I try to run the test case, It doesn´t work and display the following error:
INFO [karma]: Karma v0.12.37 server started at http://localhost:9876/
INFO [launcher]: Starting browser PhantomJS
INFO [PhantomJS 1.9.8 (Windows 8 0.0.0)]: Connected on socket NHknSeC3p_h_lf12SK Gh with id 27552356
PhantomJS 1.9.8 (Windows 8 0.0.0) Test Should be true FAILED
TypeError: 'undefined' is not an object (evaluating 'otpConfigureDatasetSelectedTab.tab')
at otpConfigureDatasetsController (D:/workspace/.....
I already have all the required files in the karma.config.js....
If I comment these two lines in the controller.
//active: otpConfigureDatasetSelectedTab.tab == 'MyReviewList'
and
//active: otpConfigureDatasetSelectedTab.tab == 'AllDatasets'
The unit testing works fine.
What´s wrong in my Spec definition???
You are not saving the object you're trying to mock. You basically have just
_otpConfigureDatasetService_ = undefined,
_otpReviewListDatasetsService_ = undefined
So it's not the controller that is failing, it's the unit test. You're injecting undefined in place of both objects. I'm not sure what you want to do exactly, but you either need to define some mock objects like _otpConfigureDatasetService_ = {tab: 'something'} or get the real services and adjust them
beforeEach(inject(function ($controller, $rootScope, otpConfigureDatasetService, otpReviewListDatasetsService) {
//
_otpConfigureDatasetService_ = otpConfigureDatasetService;
_otpReviewListDatasetsService_ = otpReviewListDatasetsService;
//
scope = $rootScope.$new();
MainCtrl = $controller('otpConfigureDatasetsController', {
$scope: scope, otpWebMapApp: _otpWebMapApp_, otpWMDeltaTracker: _otpWMDeltaTracker_, otpWMStateCache: _otpWMStateCache_,
otpConfigureDatasetService: _otpConfigureDatasetService_, otpAllDatasetsService: _otpAllDatasetsService_,
otpReviewListDatasetsService: _otpReviewListDatasetsService_, otpConfigureDatasetSelectedTab: _otpConfigureDatasetSelectedTab_
});
}));
BTW the convention is to define _object_ in the inject function and then to save it. If you inject them how they're called then you don't need to use _object_ form for your local variables.
I also prefer to use the $injector
beforeEach( inject(function($injector) {
exampleService = $injector.get('exampleService');
}));
Related
When I have one controller attached to a module, I can use mocha, karma to test it successfully. But when I add two controllers to the same module, the tests fail. Why is that?
I have 2 controllers defined on the same module. I can manually test the controllers and they work.
src/app/itemListController.js
angular.module('lazyLoad', [])
.controller('ItemListController', ['$scope', function ($scope) {
...
}]);
src/app/invoiceController.js
angular.module('lazyLoad', [])
.controller('InvoiceController', ['$scope', function ($scope) {
...
}]);
And 2 unit-tests:
test/app/itemListController.mocha.js
'use strict';
describe('testing movies', function () {
var scope;
var fixture;
beforeEach(module('lazyLoad'));
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
fixture = $controller('ItemListController', {$scope: scope});
}));
it('....', function() {});
});
test/app/invoiceController.mocha.js
'use strict';
describe('testing movies', function () {
var scope;
var fixture;
beforeEach(module('lazyLoad'));
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
fixture = $controller('InvoiceController', {$scope: scope});
}));
it('....', function() {});
});
I get:
PhantomJS 1.9.8 (Mac OS X 0.0.0) testing movies "before each" hook: workFn FAILED
the object {
"line": 1761
"message": "[ng:areq] Argument 'ItemListController' is not a function, got undefined
http://errors.angularjs.org/1.4.1/ng/areq?p0=ItemListController&p1=not%20a%20function%2C%20got%20undefined"
"name": "Error"
Now if I change the module-name for the invoiceController.js and invoiceController.mocha.js to say invoiceM then both tests work.
I must be doing something wrong...
You define your modules twice. When you use brackets [] to pass empty dependencies, you actually create a module and replace an old one if exists with the same name. What you need to do is:
// Create the module, maybe in a separate place.
angular.module('lazyLoad', []);
// Attach controllers to that module:
angular.module('lazyLoad') // HEY! SEE THIS? NO BRACKETS.
.controller('ItemListController', ...);]
angular.module('lazyLoad') // HEY! SEE THIS? NO BRACKETS.
.controller('InvoiceController' ...);
I'm going through the AngularJS PhoneCat tutorial and while the application seems to behave correctly when I click through it manually, one of the unit tests are failing for step 8.
Using Karma, I'm getting the following console output:
Chrome 42.0.2311 (Windows 7) PhoneCat controllers PhoneDetailCtrl
should fetch phone detail FAILED Error: [$injector:unpr] Unknown
provider: $routeParamsProvider <- $routeParams
The relevant part of the unit test file (controllersSpec.js) looks like this:
describe("PhoneCat controllers", function () {
describe('PhoneDetailCtrl', function () {
var scope, $httpBackend, ctrl;
beforeEach(inject(function (_$httpBackend_, $rootScope, $routeParams, $controller) {
$httpBackend = _$httpBackend_;
$httpBackend.expectGET('phones/xyz').respond({ name: 'phone xyz' });
$routeParams.phoneId = 'xyz';
scope = $rootScope.$new();
ctrl = $controller('PhoneDetailCtrl', { $scope: scope });
}));
});
});
The problem seems to be related to the declaration of the function parameter of the call to inject(). If I take $routeParams out then the script will execute.
From reading some related StackOverflow questions it seems I might be missing a dependency somewhere. The relevant parts of the karma.conf.js file look like this:
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine'],
files: [
'../lib/angular/angular.js',
'../lib/angular/angular-mocks.js',
'../lib/angular/angular-route.js',
'../app/*.js',
'unit/*.js'
],
browsers: ['Chrome'],
singleRun: false
});
};
My app.js looks like this:
var phonecatApp = angular.module("phonecatApp", [
"ngRoute",
"phonecatControllers"]);
phonecatApp.config([
"$routeProvider", function($routeProvider) {
$routeProvider
.when("/phones", {
templateUrl: "Scripts/app/partials/phone-list.html",
controller: "PhoneListCtrl"
})
.when("/phones/:phoneId", {
templateUrl: "Scripts/app/partials/phone-detail.html",
controller: "PhoneDetailCtrl"
})
.otherwise({
redirectTo: "/phones"
});
}
]);
And controllers.js looks like this:
var phonecatControllers = angular.module("phonecatControllers", []);
phonecatControllers.controller("PhoneDetailCtrl", ["$scope", "$routeParams", "$http", function ($scope, $routeParams, $http) {
$http.get("api/Phones/" + $routeParams.phoneId).success(function (data) {
$scope.phone = data;
});
}]);
As mentioned at the top, the app seems to work fine when I click through it in the browser so I'm quite confused as to where things are breaking down for the unit tests.
As is often the case with these kind of errors, the fix is pretty obvious when you see it! Basically there are two functions in controllersSpec.js, one for a list of phones and one for an individual phone's details. The phone list tests were working fine because they had
beforeEach(module("phonecatApp")); before the call to beforeEach(inject(function ());
The phone detail tests on the other hand were missing this line. Once I moved the line to the outer function the tests passed as expected.
The working controllersSpec.js looks like this:
describe("PhoneCat controllers", function () {
// This is the line that was moved.
// It existed for PhoneListCtrl but not PhoneDetailCtrl.
beforeEach(module("phonecatApp"));
describe("PhoneListCtrl", function() {
var scope, ctrl, $httpBackend;
beforeEach(module("phonecatApp"));
beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) {
// Setup
}));
// Various tests
});
describe('PhoneDetailCtrl', function () {
var scope, $httpBackend, ctrl;
beforeEach(inject(function (_$httpBackend_, $rootScope, $routeParams, $controller) {
// Setup
}));
// Various tests
});
});
I'm trying to set up testing for Angular with Karma and Jasmine. I've successfully installed and configured Karma, but I'm having trouble using angular-mocks. Below in aTest.spec.js I include a simple app, controller, and test spec to illustrate the problem. Can anyone tell me what I'm doing wrong?
My console output from Karma:
Chrome 39.0.2171 (Mac OS X 10.8.5) ControllerForTest encountered a declaration exception FAILED
TypeError: undefined is not a function
at Suite.<anonymous> (/Users/duncanmalashock/python_projects/scout/public/tests/unit/aTest.spec.js:19:20)
at jasmineInterface.describe (/Users/duncanmalashock/python_projects/scout/node_modules/karma-jasmine/lib/boot.js:59:18)
at /Users/duncanmalashock/python_projects/scout/public/tests/unit/aTest.spec.js:13:1
Chrome 39.0.2171 (Mac OS X 10.8.5): Executed 1 of 1 (1 FAILED) ERROR (0.004 secs / 0.002 secs)
karma.conf.js:
...
files: [
'http://ajax.googleapis.com/ajax/libs/angularjs/1.3.5/angular.min.js',
'angular/vendor/angular-mocks.js',
'tests/unit/*.spec.js'
],
...
Controller:
var testApp = angular.module('testApp', []);
testApp.controller('ControllerForTest', ['$scope',
function($scope) {
$scope.data = {
a: 'foo',
b: 'bar',
c: 'baz'
};
}
]);
aTest.spec.js:
describe('ControllerForTest', function() {
module('testApp');
var $controller;
inject(function(_$controller_) {
$controller = _$controller_;
});
var controller = $controller('ControllerForTest');
it('is defined', function() {
expect($controller).toBeDefined();
});
});
Module instantiation and service injection should happen in a beforeEach, not directly within a describe block. This will make them available to each of the following its.
You also don't need to test $controller, that's an Angular service. Test your controller instead.
describe('ControllerForTest', function() {
var $controller;
var ControllerForTest;
beforeEach(function() {
module('testApp');
inject(function(_$controller_) {
$controller = _$controller_;
});
});
it('is defined', function() {
// This line can also be in the beforeEach.
// Saves having to repetitively instantiate the controller.
ControllerForTest = $controller('ControllerForTest');
expect(ControllerForTest).toBeDefined();
});
});
I have a very simple controller that looks like this.
timeInOut.controller('timeInOutController', function($scope, $filter, $ionicScrollDelegate){
...
});
Whenever I try to create a unit test for it like so...
(function() {
'use strict';
var scope, controller, filter;
describe('timeInOutController', function () {
beforeEach(module('common.directives.kmDateToday'));
beforeEach(inject(function ($rootScope, $controller, $filter) {
scope = $rootScope.$new();
filter = $filter;
controller = $controller('timeInOutController', {
$scope: scope
});
}));
describe('#date setting', function(){
...
});
});
})();
I get the error:
[$injector:unpr] Unknown provider: $ionicScrollDelegateProvider <- $ionicScrollDelegate
Obviously in my example here I'm not trying to inject the $ionicScrollDelegate into the test, that's just because I've tried it any number of ways with no success and don't know which failed attempt to include.
Also in my karma.conf.js file I am including the ionic.bundle.js and angular-mocks.js libraries/files.
I can successfully unit test anything that doesn't use anything $ionic in it, so I know my testing framework is set up correctly, the issue is injecting anything ionic related.
You need to pass in all the parameters if you're going to instantiate your controller via angular. By adding the parameters you are telling angular that any time you create one of these controllers I need these things too because I am dependent upon them.
So my suggestion is to mock up some representation of these dependencies and inject them in when you are creating the controller. They do not have to be (and should not be) the actual services for your unit tests. Jasmine gives you the ability to create spy objects that you can inject so you can verify the the behavior of this unit.
(function() {
'use strict';
var scope, controller, filter, ionicScrollDelegate;
describe('timeInOutController', function () {
beforeEach(module('common.directives.kmDateToday'));
beforeEach(inject(function ($rootScope, $controller, $filter) {
scope = $rootScope.$new();
filter = $filter;
// func1 and func2 are functions that will be created as spies on ionicScrollDelegate
ionicScrollDelegate = jasmine.createSpyObj('ionicScrollDelegate', ['func1', 'func2']
controller = $controller('timeInOutController', {
$scope: scope,
$filter: filter,
$ionicScrollDelegate: ionicScrollDelegate
});
}));
describe('#date setting', function(){
...
});
});
})();
You can find more about spies via jasmine's documentation
You need to create mock objects for all dependencies your controller is using.
Take this controller as an example:
angular.module('app.module', [])
.controller('Ctrl', function($scope, $ionicLoading) {
$ionicLoading.show();
});
Here you are using the $ionicLoading service, so if you want to test this controller, you have to mock that object specifying the methods you're using in the controller:
describe('Test', function() {
// Mocks
var $scope, ionicLoadingMock;
var ctrl;
beforeEach(module('app.module'));
beforeEach(function() {
// Create $ionicLoading mock with `show` method
ionicLoadingMock = jasmine.createSpyObj('ionicLoading', ['show']);
inject(function($rootScope, $controller) {
$scope = $rootScope.$new();
ctrl = $controller('Ctrl', {
$scope: $scope,
$ionicLoading: ionicLoadingMock
});
});
});
// Your test goes here
it('should init controller for testing', function() {
expect(true).toBe(true);
});
});
I'm trying to test my application with Karma but I get following Errors:
minErr/<#/home/usr/webui-ng/src/client/app/bower_components/angular/angular.js:78:5
loadModules/<#/home/usr/webui-ng/src/client/app/bower_components/angular/angular.js:3859:1
forEach#/home/usr/webui-ng/src/client/app/bower_components/angular/angular.js:325:7
loadModules#/home/usr/webui-ng/src/client/app/bower_components/angular/angular.js:3824:5
createInjector#/home/usr/webui-ng/src/client/app/bower_components/angular/angular.js:3764:3
workFn#/home/usr/webui-ng/src/client/app/bower_components/angular-mocks/angular-mocks.js:2150:9
These are my files:
hello.js
angular.module('myApp', [])
.controller('MainController', function($scope) {
$scope.name = "Ari";
$scope.sayHello = function() {
$scope.greeting = "Hello " + $scope.name;
}
})
hello.js - test file :
describe('Unit: MainController', function() {
// Load the module with MainController
beforeEach(module('myApp'));
var ctrl, scope;
// inject the $controller and $rootScope services
// in the beforeEach block
beforeEach(inject(function($controller, $rootScope) {
// Create a new scope that's a child of the $rootScope
scope = $rootScope.$new();
// Create the controller
ctrl = $controller('MainController', {
$scope: scope
});
}));
it('should create $scope.greeting when calling sayHello',
function() {
expect(scope.greeting).toBeUndefined();
scope.sayHello();
expect(scope.greeting).toEqual("Hello Ari");
});
});
As you can see, I've already loaded the module, as the solution was in Controller undeclared in Jasmine test.
What else could it be? I haven't found much about these errors on the web. I would be very happy to finally find an answer for that.
As runTarm mentioned, the path in the karma.conf.js was not set to the actual path of the script file (hello.js).