I'm trying to test my app, which has each controller defined on it's own module i.e., not as a controller of the app module, and then loaded as a dependency of the main app module. When I try to run a test that just checks that the loginController is defined, using Karma/Jasmine, I get the following output:
'Expected undefined to be defined.'
edit
I updated login.controller.spec and switched the karma browser to chrome, which gave me more useful debug info. Now I'm getting an error related to a factory that is added to $httpProvider.interceptors in the main app file:
Unknown provider: authFactoryProvider <- authFactory <- $http <- $translateStaticFilesLoader <- $translate
I found similar issues related to this which were resolved by including angular-translate-loader-static-files.js, which is being loaded when karma runs:
DEBUG [web-server]: serving (cached): /path/to/my/app/bower_components/angular-translate-loader-static-files/angular-translate-loader-static-files.js
How do I resolve all these dependency issues with karma?
index.js
'use strict';
angular.module('app',
[
//'mockBackend', //uncomment when loading mock backend
'ngAnimate',
'ngCookies',
'ngTouch',
'ngSanitize',
'ngResource',
'ui.bootstrap',
'ui.router',
'ui.router.stateHelper',
'pascalprecht.translate',
'utilsModule',
'loginModule',
'vsmsPackageModule',
'vsmsCampaignModule',
'vdmsCampaignModule',
'vdmsDashboardModule',
'daterangepicker',
'ui.event',
'idmAdminModule',
'helpdeskModule',
'ncy-angular-breadcrumb',
'rzModule',
'vsmsDashboardModule',
'highcharts-ng',
'permission',
'dndLists'
])
.config(function ($stateProvider, $urlRouterProvider, $httpProvider, $locationProvider, $translateProvider, $breadcrumbProvider, $compileProvider) {
$urlRouterProvider.otherwise('/');
//initialize get if not there
if (!$httpProvider.defaults.headers.get) {
$httpProvider.defaults.headers.get = {};
}
//disable IE ajax request caching
$httpProvider.defaults.headers.get['If-Modified-Since'] = 'Mon, 26 Jul 1997 05:00:00 GMT';
// extra
$httpProvider.defaults.headers.get['Cache-Control'] = 'no-cache';
$httpProvider.defaults.headers.get['Pragma'] = 'no-cache';
$locationProvider.html5Mode({
enabled: false,
requireBase: false
});
$translateProvider.useSanitizeValueStrategy('sanitize');
$translateProvider.useStaticFilesLoader({
prefix: '/locales/',
suffix: '.json'
});
$translateProvider
.preferredLanguage('en_us')
.fallbackLanguage('en_us');
$breadcrumbProvider.setOptions({
templateUrl: 'components/templates/breadcrumb.tpl.html'
});
$compileProvider.debugInfoEnabled(false);
// $compileProvider.aHrefSanitizationWhitelist(/^\s*(|blob|):/);
$httpProvider.interceptors.push('authFactory');
$httpProvider.interceptors.push('headersFactory');
})
login.module.js
angular.module('loginModule', []);
login.controller.js
angular.module('loginModule')
.controller('loginController', login);
login.$inject = [
'$log',
'$uibModal',
'$rootScope',
'storageFactory',
'loginFactory',
'$state',
'RoleStore',
'PermissionStore'
];
function login($log, $uibModal, $rootScope, storageFactory, loginFactory, $state, RoleStore, PermissionStore) {
/* jshint validthis: true */
var vm = this;
vm.loginUser = loginUser;
vm.forgotPassword = forgotPassword;
vm.errorCode = null;
PermissionStore.clearStore();
function loginUser() {
...
I'm just trying to test if the controller exists and I can't get past the error:
Expected undefined to be defined.
login.controller.spec.js
describe('loginController', function() {
beforeEach(module('app'));
var $controller,
$scope,
$log,
$uibModal,
$rootScope,
storageFactory,
loginFactory,
$state,
RoleStore,
PermissionStore,
vsmsCoreFactory;
beforeEach(inject(function(_$controller_, _$log_, _$uibModal_, _$rootScope_, _storageFactory_, _loginFactory_, _$state_, _RoleStore_, _PermissionStore_, _vsmsCoreFactory_){
$controller = _$controller_;
$scope = $rootScope.new();
$log = _$log_;
$uibModal = _$uibModal_;
$rootScope = _$rootScope_;
storageFactory = _storageFactory_;
loginFactory = _loginFactory_;
$state = _$state_;
RoleStore = _RoleStore_;
PermissionStore = _PermissionStore_;
vsmsCoreFactory = _vsmsCoreFactory_;
}));
describe('vm.loginUser', function() {
it('should be defined', function() {
var loginController = $controller('loginController', {
$log: $log,
$uibModal: $uibModal,
$rootScope: $rootScope,
storageFactory: storageFactory,
loginFactory: loginFactory,
$state: $state,
RoleStore: RoleStore,
PermissionStore: PermissionStore,
vsmsCoreFactory: vsmsCoreFactory
});
expect(loginController).toBeDefined();
// expect(testController.model.name).toEqual("controllerAs vm test");
});
});
});
unit-tests.js
'use strict';
var gulp = require('gulp');
var $ = require('gulp-load-plugins')();
var wiredep = require('wiredep');
var paths = gulp.paths;
function runTests (singleRun, done) {
var bowerDeps = wiredep({
directory: 'bower_components',
exclude: ['bootstrap-sass-official'],
dependencies: true,
devDependencies: true
});
var testFiles = bowerDeps.js.concat([
'./src/app/index.js',
'./src/components/scripts/ui-bootstrap-custom-tpls-2.1.3.js',
'./src/{app,components}/**/*.module.js',
'./src/{app,components}/**/*.factory.js',
'./src/{app,components}/**/*.controller.js',
'./src/{app,components}/**/*.spec.js'
]);
gulp.src(testFiles)
.pipe($.karma({
configFile: 'karma.conf.js',
action: (singleRun)? 'run': 'watch'
}))
.on('error', function (err) {
// Make sure failed tests cause gulp to exit non-zero
throw err;
});
}
gulp.task('test', function (done) { runTests(true /* singleRun */, done) });
gulp.task('test:auto', function (done) { runTests(false /* singleRun */, done) });
Please refer the the below link it is already answered.
How to inject controller dependencies in Jasmine tests?
loginController dependencies are not passed in your unit test.
The second parameter of $controller is for controller dependencies.
Empty dependency is passed.
$controller('loginController', {});
Make sure all the dependent modules are loaded before testing loginColtroller.
I have modified your code. I hope it will work.
'use strict';
describe('loginController', function() {
beforeEach(module('loginModule'));
var loginController;
beforeEach(inject(function($controller, _$log_, _$uibModal_, _$rootScope_, _storageFactory_, _loginFactory_, _$state_, _RoleStore_, _PermissionStore_ ){
scope = _$rootScope_.$new();
loginController = $controller('loginController',
{ // all dependencies has to be passed in order
'$log' : _$log_,
'$uibModal' : _$uibModal_,
'$rootScope' : _$rootScope_,
'storageFactory' : _storageFactory_,
'loginFactory': _loginFactory_,
'$state': _$state_,
'RoleStore': _RoleStore_,
'PermissionStore': _PermissionStore_
},
{});
}));
it('should be defined', function() {
expect(loginController).toBeDefined();
});
});
The issue is with your login.module.js file.
You'll have to inject ui.bootstrap and ui.router as dependencies while defining the loginModule. Otherwise it will not be able to get $uibModal and $state while defining the loginController.
This should be the definition of your loginModule
angular.module('loginModule', ['ui.bootstrap', 'ui.router']);
PS: I'm assuming here that storageFactory, loginFactory, RoleStore and PermissionStore are defined on loginModule itself.
Hope this helps!
Related
I've included 3 files in my karma config : 1. angular.js, angular-mock.js, and login.spec.js
this is my login.spec.js:
describe("Hello World example", function() {
beforeEach(module("app"));
var loginCtrl,
scope;
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
loginCtrl = $controller('loginCtrl', {
$scope: scope
});
}));
it('says hello world!', function () {
expect(scope.hello).toEqual("Hello");
});
});
so my login.js (controller) look like this
angular.module('app')
.controller('loginCtrl', function($scope, $rootScope, $location) {
$scope.hello = 'hello';
});
but I got Module 'app' is not available!
I don't see you creating the app module anywere. You should include the app.js and all the sources your tests need to your karma.json as well.
You just need to add this line to the top of your login.js file:
This declares the module:
angular.module('app', []);
This gets an instance of the module(as you are doing):
angular.module('app')
.controller('loginCtrl', function($scope, $rootScope, $location) {
$scope.hello = 'hello';
});
Here is a working Plunkr that is very similar:
http://embed.plnkr.co/O1otV47VxsIL5krAdUW0/preview
You also mentioned that you had added 3 files to the karma config and didn't mention the login.js. That will also need to be added.
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 got a problem when I tried to test my controller. When I run my test I got an error
Error: [ng:areq] Argument 'MainCtrl' is not a function, got undefined http://errors.angularjs.org/1.3.8/ng/areq?p0=MainCtrl&p1=not%20a%20function%2C%20got%20undefined
at assertArg (/Users/tetianachupryna/project/bower_components/angular/angular.js:1577)
at assertArgFn (/Users/tetianachupryna/project/bower_components/angular/angular.js:1588)
at /Users/tetianachupryna/project/bower_components/angular/angular.js:8418
at /Users/tetianachupryna/project/src/spec/controllers/main-controller.spec.js:11
at /Users/tetianachupryna/project/src/spec/controllers/main-controller.spec.js:17
at /Users/tetianachupryna/project/node_modules/karma-jasmine/lib/adapter.js:184
at http://localhost:9877/karma.js:185
at http://localhost:9877/context.html:51
I know that SO is full of similar questions. But I'm a total null in Angular and JS in general, so those answers didn't help me. From similar questions on SO I discovered that my problem is in wrong definition of the controller but I still can't figure out what I did wrong. I've stack and I'm begging for your help.
First of all here is my src/app/index.js file where my module is defined
var app = angular.module('myModule', [
'ngAnimate',
'ngSanitize',
'ngResource',
'ui.router',
'pascalprecht.translate',
'thing1',
'thing2']);
Here is src/app/controllers/main-controller.js
angular.module('myModule').controller('MainCtrl', [
'$scope',
'$state',
function ($scope, $state) {
$scope.state = $state;
//***
$scope.isBigStep = function isBigStep() {
return $state.$current.step == 3;
};
}]);
And finally this a file with the test src/spec/controllers/main-controller.spec.js
describe('MainCtrl', function() {
var scope, $state, createController;
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
createController = function() {
return $controller('MainCtrl', {
'$scope': scope
});
};
}));
it('should make 3 as current step', function() {
var controller = createController();
expect(scope.isBigStep()).toBe(true);
});
});
In karma config I have all those files
files: [
'bower_components/angular/angular.js',
'bower_components/angular-mocks/angular-mocks.js',
'src/app/index.js',
'src/app/controllers/*.js',
'src/spec/controllers/*.js'
],
For run my test I use karma-runner plugin in RubyMine.
I'd be thankful for any help!
What you are missing is to add the module in the beforeEach hook for test setup. Otherwise the controller will not be found. So add beforeEach(module('myModule')).
describe('MainCtrl', function() {
var scope, $state, createController;
beforeEach(module('myModule')); //<--- Hook module
beforeEach(inject(function ($rootScope, $controller) {
scope = $rootScope.$new();
createController = function() {
return $controller('MainCtrl', {
'$scope': scope
});
};
}));
it('should make 3 as current step', function() {
var controller = createController();
expect(scope.isBigStep()).toBe(true);
});
});
I have been stuck on this issue for a bit and have not had success with any other questions on here regarding testing uiRouter with requirejs. I have a fairly basic controller setup that uses the $state.go to transition between states once a button is clicked.
runsController.js
define([],
function() {
"use strict"
var runsController = function($scope, $window, $http, $state) {
function getRuns() {
$http.get($window.apiLinks.runs).success(function(data) {
$scope.runs = data.objects;
}).error(function(data) {
console.error();
console.error(data);
});
}
getRuns();
$scope.refresh = getRuns;
$scope.createRun = function() {
//$state.go('createRun');
}
return ["$scope", "$window", "$http", "$state", runsController];
});
The controller is added to an app that depends on uiRouter.
app.js
define(["angular", "js/controllers/runsController", "js/router", "uiRouter"],
function(angular, runsController, router) {
'use strict'
var appName = "myApp";
var app = angular.module(appName, ["ui.router"]);
app.config(router);
app.controller("runsController", runsController);
function getName() {
return appName;
}
return {
app : app,
getName : getName
};
});
router.js
define(["./controllers/runsController"],
function(runsController){
var routes = function($stateProvider, $urlRouterProvider) {
// For any unmatched url, redirect to /runs
$urlRouterProvider.otherwise("/runs");
// Set up the states
$stateProvider
.state('runs', {
url: "/runs",
templateUrl: "partials/runs.html",
controller: "runsController"
})
.state('createRun', {
url: "/createRun",
templateUrl: "partials/runCreator.html"
});
};
return ["$stateProvider", "$urlRouterProvider", routes];
});
Here's the test I have setup for this controller:
define(["angular", "angularMocks", "js/app", "js/controllers/runsController"],
function(angular, mocks, app, runsController) {
'use strict'
describe('runsController Unit Tests', function() {
var mockApp, scope, httpBackend, objects, state;
objects = [
{rid : 1, filename : "myFile.txt", exitCode : 0},
{rid : 2, filename : "test.zip", exitCode : 0},
{rid : 3, filename : "test2.tar", exitCode : 0}
];
beforeEach(function() {
mockApp = angular.module(app.getName());
});
beforeEach(inject(function ($rootScope, $controller, $httpBackend, $http, $state) {
scope = $rootScope.$new();
window.apiLinks = {"runs" : "/mock/api/runs"};
httpBackend = $httpBackend;
state = $state;
httpBackend.when("GET", "/mock/api/runs").respond({
"objects" : objects
});
$controller(runsController[4], {
$scope : scope,
$window : window,
$http : $http,
$state : $state
});
httpBackend.flush();
}));
it("Get state working in test environment", function() {
});
it("Test that controller automatically gets the runs", function() {
// Because of the way our controller files are setup, we have to specify the
// last element in the array instead of just
expect(scope.runs.length).toBe(3);
});
});
This is currently giving me an error message of: Error: [$injector:unpr] Unknown provider: $stateProvider <- $state
From what I have been reading I believe this means I need to be injecting the dependencies that the controller needs in order to run state, but if I am using the app before every test I run then the dependencies should be in there when the .config() is done right? Can anybody help me figure out what it is I am not seeing with this?
Thanks in advance!
I had this same issue this is what I came up with.
This is very important:
karma.conf.js
{
pattern: 'ROOT/TO/YOUR/angular-ui-router/release/angular-ui-router.js',
included: false
},
test-main.js : This is your karma - require - config file
paths: {
// External libraries
'angular': '/base/PATH/TO/YOUR/angular/angular',
'angularMocks': '/base/PATH/TO/YOUR/angular-mocks/angular-mocks',
'uiRouter' : '/base/PATH/TO/YOUR/angular-ui-router/release/angular-ui-router',
.....
/base/ + your path
DO THE SHIMMY
shim: {
'angular': { 'exports': 'angular' },
'angularMocks': { deps: ['angular'], 'exports': 'angular.mock' },
'uiRouter' : {deps: ['angular'], 'exports' : 'uiRouter'},
Now we are inside out test.
define the module
define([
'angular',
'angularMocks',
'uiRouter',
.....
Now when you set up your beforeEach
BeforeEach(inject(function ($injector){
$state = $injector.get('$state');
....
Hope this helps you out.
I met a problem with testing my controllers. I load them dynamically (lazy load, see after) so I wrote them like this :
angular.module('myApp').controllerProvider.register('DashboardCtrl', [
'$scope', function ($scope) {
$scope.foo = 'bar';
}
]);
I initialise my module like this :
var app = angular.module('myApp', [
'ngRoute',
'ngCookies',
'ngResource',
'ngSanitize',
'ui.router'
]);
app.run([
'$rootScope', '$state', '$stateParams',
function ($rootScope, $state, $stateParams) {
$rootScope.$state = $state;
$rootScope.$stateParams = $stateParams;
}
]);
app.config(function ($stateProvider, $urlRouterProvider, $controllerProvider, $compileProvider, $filterProvider, $provide) {
app.stateProvider = $stateProvider;
app.routeProvider = $urlRouterProvider;
app.controllerProvider = $controllerProvider;
app.compileProvider = $compileProvider;
app.filterProvider = $filterProvider;
app.provide = $provide;
$urlRouterProvider.otherwise('/');
$stateProvider
.state('dashboard', {
url : '/',
templateUrl : 'views/dashboard.html',
controller : 'DashboardCtrl',
resolve : {
deps : function ($q, $rootScope) {
var deferred = $q.defer();
curl('scripts/controllers/dashboard.js')
.then(function () {
$rootScope.$apply(function () {
deferred.resolve({});
});
});
return deferred.promise;
}
}
});
});
That works. Method is described there : http://ify.io/lazy-loading-in-angularjs/
But when I want to test, I get each time the error : Error: [ng:areq] Argument 'DashboardCtrl' is not a function, got undefined
My test script :
describe('Controller: DashboardCtrl', function () {
'use strict';
var DashboardCtrl,
scope;
// load the controller's module
beforeEach(function () {
var app = angular.module('cellierApp', [
'ngRoute',
'ngCookies',
'ngResource',
'ngSanitize',
'ui.router'
]);
app.run([
'$rootScope', '$state', '$stateParams',
function ($rootScope, $state, $stateParams) {
$rootScope.$state = $state;
$rootScope.$stateParams = $stateParams;
}
]);
app.config(function ($stateProvider, $urlRouterProvider, $controllerProvider, $compileProvider, $filterProvider, $provide) {
app.stateProvider = $stateProvider;
app.routeProvider = $urlRouterProvider;
app.controllerProvider = $controllerProvider;
app.compileProvider = $compileProvider;
app.filterProvider = $filterProvider;
app.provide = $provide;
});
inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
DashboardCtrl = $controller('DashboardCtrl', {
$scope : scope
});
});
});
it('should attach a list of awesomeThings to the scope', function () {
expect(scope.foo).toBeDefined();
expect(scope.foo).toBe('bar');
});
});
I don't know what I'm doing wrong. Is there someone who knows ? Thanks in advance
I'm not sure whether Karma has support for Script.js loading, but for sure it has support for Require.js.
Since you followed that tutorial, have you checked out the final example he gave on github (It contains unit testing as well)? The idea is that you can convert everything to use Require.js instead of Script.js and use Karma with karma-requirejs for the testing.
Although it is not too difficult, there are quite a lot of changes. I won't post the working code here, but I would suggest reading the following:
http://ify.io/unit-testing-lazily-loaded-angularjs-artefacts/ and http://karma-runner.github.io/0.8/plus/RequireJS.html
I can't link the final example because I don't have the reputation, but it's at the bottom of the tutorial you've linked.
To see a runnable example using Asynchronous Module Definitions with RequireJS, have a look at the sample app."
You don't load modules, but override the angular modules in this snippet:
var app = angular.module('cellierApp', [
'ngRoute',
'ngCookies',
'ngResource',
'ngSanitize',
'ui.router'
]);
The proper call would be
beforeEach(function() {
// the module to be tested
module('cellierApp');
});
Also, the var declaration of the app is very crappy imo. You have angular as a container, use it, don't create objects on global closure for no reason. Change this:
var app = angular.module('myApp', [
'ngRoute',
'ngCookies',
'ngResource',
'ngSanitize',
'ui.router'
]);
app.run...
To:
angular.module('myApp', [
'ngRoute',
'ngCookies',
'ngResource',
'ngSanitize',
'ui.router'
])
.config([...
.run([....
The unit test itself can be simplified so much:
describe('Controller: DashboardCtrl', function() {
'use strict';
var scope;
// load the controller's module
beforeEach(function() {
module('cellierApp');
});
beforeEach(inject(function($controller, $rootScope) {
scope = $rootScope.$new();
$controller('DashboardCtrl', {
$scope: scope
});
}));
it('should attach a list of awesomeThings to the scope', function() {
expect(scope.foo).toBeDefined();
expect(scope.foo).toBe('bar');
});
});
In your test you are not registering your controller
put your code for creating you controller in your test
app.controllerProvider.register('DashboardCtrl', [
'$scope', function ($scope) {
$scope.foo = 'bar';
}
]);
however depending on your test setup your app and controller files should be loaded through your test system and you should use
beforeEach(module('cellierApp'));
instead of creating your app directly in your test