devastato devastato - 4 months ago 17
AngularJS Question

Angular/Karma Unit Test errors after 14 tests - regardless of the test itself

The general Problem



We are building a AngularJS Application and utilize karma/jasmine for unit tests.
After we got karma running and were able to successfully run tests, we ran into a really odd error.

For some reason, we cannot run more than 14 separate tests.
Somehow the number seems to be relevant, as it doesn't matter which tests I remove or add.

There are currently two controllers and one service, that have tests.

When we add any more test to any of the specs,
gulp test-unit
will fail with an injector error, where it states, it cannot load a module, without much specifics.
Even when the new test is only
expect(true).toEqual(true);

As I wrote, it doesn't matter where and which test we add, it always fails with more than 14 tests.

Even when we remove all tests and just to the "true=true" test.
I could not find anything in the karma config, which limits the number of tests that can be run. Nor did I find anything similar on the web.
What do I not see?

(I am aware of the inconsistent naming)

Update:



I have now observed, that the tests, according to karma's debug log, run successful. Nonetheless, the error still appears. But it might not be a karma issue, after all.

This is our Karma config:



karma-unit.conf.js



var sharedConfig = require('./karma-shared.conf');

module.exports = function (config) {
var conf = sharedConfig();

conf.files = conf.files.concat([

//extra testing code
'bower_components/angular-mocks/angular-mocks.js',

//mocha stuff
'tests/mocha.conf.js',

//test files
'./tests/unit/**/*.js'
]);

config.set(conf);
};


karma-shared.conf.js



module.exports = function() {
return {
basePath: '../',
frameworks: ['jasmine','mocha'],
reporters: ['progress'],
browsers: ['Chrome', 'Safari'],
autoWatch: true,

// these are default values anyway
singleRun: false,
colors: true,

files : [
//3rd Party Code

//App-specific Code
'build/vendor.js',
'build/vendor.ui.js',
'build/vendor.datatables.js',
'build/app.js',

//Test-Specific Code

]
};
};


Those are the three Specs:



LoginCtrlSpec.js



describe('LoginController', function () {
var loginCtrl`enter code here`
, scope
, UserService
, $controller
, $rootScope
, validUser = 'valid@email.tld'
, inValidUser = 'someinvaliduser@nowhereto.go'
, password = 'superst(r/l)ongpassword'
, backendIsFunctional = true
, theCookies = {}
;
var userObject = {
email: validUser,
first_name: 'Maria',
last_name: 'Tortilla',
id: 1,
authentication_token: 'thisisaveryvalidtoken'
};
// load other modules and provide services
beforeEach(function () {
module('app.auth');
module('ui.router');
module(function ($provide) {
/**/
$provide.service('UserService', function () {
this.login = function (email, password) {
//prepare responses for success and failure
var successResponse = {
first_name: 'Maria',
last_name: 'Tortilla',
authentication_token: '69XyesVbU4ja8HYGtTN4',
authentication_token_created_at: '2016-07-20T11:34:42.567Z',
has_changed_password: true,
data: {
user: {}
}
};
var failureResponse = {
status: 401,
data: {
user: {}
}
};
//check credentials
if (
email === 'valid@email.tld' &&
password === 'superst(r/l)ongpassword'
) {
//return success response
return {
then: function (success, error) {
success(successResponse);
}
}
} else {
//return error response
return {
then: function (success, error) {
error(failureResponse);
}
}
}
};
this.getCurrentUser = function () {
return {
then: function (success, error) {
if (backendIsFunctional) {
success(userObject);
} else {
error({});
}
}
}
};
this.updateCurrentUser = function () {
return {
then: function (callback) {
callback(userObject);
}
}
};
this.setCurrentUser = function (user) {
this.currentUser = user;
};
this.currentUser = {};
});
/**/
$provide.service('lazyScript', function () {
return {
register: function (args) {
return {
then: function (callback) {
}
}
}
}
});
/**/
$provide.service('$state', function () {
this.go = jasmine.createSpy();
});
/**/
$provide.service('$cookies', function () {
return {
putObject: function (name, data) {
// console.log('put '+name+' with '+data);
theCookies[name] = data;
},
remove: function (name) {
// console.log('removed cookie '+name);
delete theCookies[name];
},
getObject: function (name) {
return theCookies[name];
}
};
});
/**/
});
});
// inject dependencies
beforeEach(inject(function ($injector) {
$controller = $injector.get('$controller');
$rootScope = $injector.get('$rootScope');
// $state = $injector.get('$state');
scope = $rootScope.$new();
loginCtrl = $controller('LoginCtrl', {
'$scope': scope
});
}));
// perform some tests
/**/
it('forwards the user to the dashboard after successful login', function () {
expect(loginCtrl).not.toBe(null);
spyOn(scope, 'finishAndEnter');
scope.email = validUser;
scope.password = password;
scope.login();
$rootScope.$apply();
expect(scope.finishAndEnter).toHaveBeenCalled();
});
/**/
it('denies access with invalid credentials', function () {
spyOn(scope, 'finishAndEnter');
scope.email = inValidUser;
scope.login();
$rootScope.$apply();
expect(scope.finishAndEnter).not.toHaveBeenCalled();
});
/**/
it('should not let the user complete her profile without first or last name', function () {
spyOn(scope, 'demandPersonalInfo');
scope.updateDetails = {first_name: undefined, last_name: undefined};
scope.updateUserDetails();
expect(scope.demandPersonalInfo).toHaveBeenCalled();
});
/**/
it('should not let the user complete her profile without only first name', function () {
spyOn(scope, 'demandPersonalInfo');
scope.updateDetails = {first_name: 'Maria', last_name: undefined};
scope.updateUserDetails();
expect(scope.demandPersonalInfo).toHaveBeenCalled();
});
/**/
it('should not let the user complete her profile without only last name', function () {
spyOn(scope, 'demandPersonalInfo');
scope.updateDetails = {first_name: undefined, last_name: 'Tortilla'};
scope.updateUserDetails();
expect(scope.demandPersonalInfo).toHaveBeenCalled();
});
/**/
it('should let a user update her profile with first and last name', function () {
spyOn(scope, 'demandPersonalInfo');
scope.updateDetails = {first_name: 'Maria', last_name: 'Tortilla'};
console.log(scope.updateDetails);
scope.updateUserDetails();
expect(scope.demandPersonalInfo).not.toHaveBeenCalled();
});
/**/
});


AdminControllerSpec.js



describe('Unit: testing AdministrationController Controller module', function () {
'use strict';
//references to mocked services
var mockUserService;


beforeEach(function () {
//mock userservice
module(function ($provide) {
$provide.service('UserService', function () {
this.getCurrentUser = jasmine.createSpy('getCurrentUser');
});
});

//load admin module
module('app.admin');
});

var adminCtrl, scope;

beforeEach(inject(function ($controller, $rootScope, UserService) {

//catch injected UserService and assign to mocked one
mockUserService = UserService;

scope = $rootScope.$new();
adminCtrl = $controller('AdministrationController', {
'$scope': scope
});
}));

describe('AdministrationController', function () {

it('AdministrationController should be existing', function () {
expect(adminCtrl).not.toBe(null);
});
it('means that hello is hello', function () {
expect(scope.hello).toEqual('Hello');
});
it('is helgig', function () {
//call a method that uses the userservice
scope.login();

//the userservice should have been called from login()
expect(mockUserService.getCurrentUser).toHaveBeenCalled();

});
});

});


UserServiceSpec.js



describe('UserService', function () {

var UserService;

var validUserName = 'validuser@domain.tld';
var validUserPassword = 'goopassword';
var inValidUserName = 'invaliduser@domain.tld';
var backendIsWorking = true;
// var qReturnesUser = false;
var validUser = {
'email': 'validuser@domain.tld',
'first_name': 'Maria',
'last_name': 'Tortilla',
'authentication_token': 'yeahSomeT0k3nIThink_dontYu?'
};

var updatedUser = {
'first_name': 'Harro',
'email': 'validuser@domain.tld',
'last_name': 'Tortilla',
'authentication_token': 'yeahSomeT0k3nIThink_dontYu?'
};

var updateUserResponse = {
status: 200,
user: updatedUser
};

var httpSuccessResponse = {
data: validUser
};
var httpErrorResponse = {
data: {
status: 401,
user: {}
}
};

//mocked cookie dictionary
var theCookies = {};

//load modules
beforeEach(function () {
module('ngResource');
module('app.common');
module(function ($provide) {

$provide.service('$q', function () {
return {
defer: function () {
return {
resolve: function () {
},
reject: function () {
},
promise:{
then: function(){}
}
};
}
};
});
$provide.service('$http', function () {
return function (config) {

var pw = config.data.password;
var user = config.data.email;

var responseObject = {};

//login and logout response
if (config.url.indexOf('sign_in') >= 0 || config.url.indexOf('sign_out') >= 0) {
responseObject = {
//success portion of response
success: function (callback) {
var response;

//decide which actual response should be returned
if (pw === validUserPassword && user === validUserName) {
response = httpSuccessResponse;
callback(response);
} else {
response = httpErrorResponse;
}
//return chained error portion
return {
error: function (callback) {
callback(response);
}
}
}
};
}

return responseObject;
}
});
$provide.service('$cookies', function () {
return {
putObject: function (name, data) {
// console.log('put '+name+' with '+data);
theCookies[name] = data;
},
remove: function (name) {
// console.log('removed cookie '+name);
delete theCookies[name];
},
getObject: function(name){
return theCookies[name];
}
};
});
$provide.factory('User', function () {
return {
update: function (user, successCallback, errorCallback) {

if (backendIsWorking) {
successCallback(updateUserResponse);
} else {
errorCallback(httpErrorResponse);
}
},
get: function (user, successCallback, errorCallback) {
if (backendIsWorking) {
successCallback(validUser);
} else {
errorCallback(httpErrorResponse);
}
}
}
});
});
});

//inject stuff
beforeEach(inject(function ($injector) {
// UserService = _UserService_;
$resource = $injector.get('$resource');
UserService = $injector.get('UserService');
}));

//do the tests
it('should have UserSerivce defined', function () {
// console.log(UserService);
expect(UserService).toBeDefined();
});

it('should have a user object after login', function () {
UserService.login(validUserName, validUserPassword);
expect(UserService.currentUser).toBeDefined();
expect(UserService.currentUser).toEqual(validUser);
});

it('should have a empty user object after invalid login', function () {
UserService.login(inValidUserName, validUserPassword);
expect(UserService.currentUser).toBeDefined();
expect(UserService.currentUser).toEqual(null);
});

it('should clear the user object after logout', function () {
UserService.currentUser = validUser;
expect(UserService.currentUser).toBeDefined();
UserService.logout();
expect(UserService.currentUser).toBe(null);
});

it('should set the current user', function () {
expect(UserService.currentUser).toBe(null);
UserService.setCurrentUser(validUser);
expect(UserService.currentUser).toEqual(validUser);
});

it('should update the users data', function () {
spyOn(UserService, 'setCurrentUser');
UserService.currentUser = validUser;
UserService.updateCurrentUser();
expect(UserService.setCurrentUser).toHaveBeenCalledWith(updatedUser);
});
});


Finally, this is the Error we get:

#

Regardless of which test we remove, it always is exactly the same error.

Error: [$injector:modulerr] http://errors.angularjs.org/1.5.8/$injector/modulerr?p0=app&p1=%5B%24injector%3Amodulerr%5D%20http%3A%2F%2Ferrors.angularjs.org%2F1.5.8%2F%24injector%2Fmodulerr%3Fp0%3DngCookies%26p1%3D%255B%2524injector%253Anomod%255D%2520http%253A%252F%252Ferrors.angularjs.org%252F1.5.8%252F%2524injector%252Fnomod%253Fp0%253DngCookies%250Ahttp%253A%252F%252Flocalhost%253A9876%252Fbase%252Fbuild%252Fvendor.js%253F9b2b4c4d081dd795c95280a4f7af90157c7ae917%253A129%253A417%250Ahttp%253A%252F%252Flocalhost%253A9876%252Fbase%252Fbuild%252Fvendor.js%253F9b2b4c4d081dd795c95280a4f7af90157c7ae917%253A148%253A100%250Ab%2540http%253A%252F%252Flocalhost%253A9876%252Fbase%252Fbuild%252Fvendor.js%253F9b2b4c4d081dd795c95280a4f7af90157c7ae917%253A147%253A143%250Ahttp%253A%252F%252Flocalhost%253A9876%252Fbase%252Fbuild%252Fvendor.js%253F9b2b4c4d081dd795c95280a4f7af90157c7ae917%253A147%253A386%250Ahttp%253A%252F%252Flocalhost%253A9876%252Fbase%252Fbuild%252Fvendor.js%253F9b2b4c4d081dd795c95280a4f7af90157c7ae917%253A162%253A473%250Aq%2540http%253A%252F%252Flocalhost%253A9876%252Fbase%252Fbuild%252Fvendor.js%253F9b2b4c4d081dd795c95280a4f7af90157c7ae917%253A130%253A359%250Ag%2540http%253A%252F%252Flocalhost%253A9876%252Fbase%252Fbuild%252Fvendor.js%253F9b2b4c4d081dd795c95280a4f7af90157c7ae917%253A162%253A320%250Ahttp%253A%252F%252Flocalhost%253A9876%252Fbase%252Fbuild%252Fvendor.js%253F9b2b4c4d081dd795c95280a4f7af90157c7ae917%253A162%253A489%250Aq%2540http%253A%252F%252Flocalhost%253A9876%252Fbase%252Fbuild%252Fvendor.js%253F9b2b4c4d081dd795c95280a4f7af90157c7ae917%253A130%253A359%250Ag%2540http%253A%252F%252Flocalhost%253A9876%252Fbase%252Fbuild%252Fvendor.js%253F9b2b4c4d081dd795c95280a4f7af90157c7ae917%253A162%253A320%250Acb%2540http%253A%252F%252Flocalhost%253A9876%252Fbase%252Fbuild%252Fvendor.js%253F9b2b4c4d081dd795c95280a4f7af90157c7ae917%253A166%253A337%250Ac%2540http%253A%252F%252Flocalhost%253A9876%252Fbase%252Fbuild%252Fvendor.js%253F9b2b4c4d081dd795c95280a4f7af90157c7ae917%253A143%253A392%250ABc%2540http%253A%252F%252Flocalhost%253A9876%252Fbase%252Fbuild%252Fvendor.js%253F9b2b4c4d081dd795c95280a4f7af90157c7ae917%253A144%253A180%250Ahttp%253A%252F%252Flocalhost%253A9876%252Fbase%252Fbuild%252Fapp.js%253F3b907e783e3f83ec76cf288ed98d542361f9ed3a%253A287%253A22%250Aj%2540http%253A%252F%252Flocalhost%253A9876%252Fbase%252Fbuild%252Fvendor.js%253F9b2b4c4d081dd795c95280a4f7af90157c7ae917%253A2%253A26930%250AfireWith%2540http%253A%252F%252Flocalhost%253A9876%252Fbase%252Fbuild%252Fvendor.js%253F9b2b4c4d081dd795c95280a4f7af90157c7ae917%253A2%253A27739%250Aready%2540http%253A%252F%252Flocalhost%253A9876%252Fbase%252Fbuild%252Fvendor.js%253F9b2b4c4d081dd795c95280a4f7af90157c7ae917%253A2%253A29543%250AI%2540http%253A%252F%252Flocalhost%253A9876%252Fbase%252Fbuild%252Fvendor.js%253F9b2b4c4d081dd795c95280a4f7af90157c7ae917%253A2%253A29728%0Ahttp%3A%2F%2Flocalhost%3A9876%2Fbase%2Fbuild%2Fvendor.js%3F9b2b4c4d081dd795c95280a4f7af90157c7ae917%3A129%3A417%0Ahttp%3A%2F%2Flocalhost%3A9876%2Fbase%2Fbuild%2Fvendor.js%3F9b2b4c4d081dd795c95280a4f7af90157c7ae917%3A163%3A224%0Aq%40http%3A%2F%2Flocalhost%3A9876%2Fbase%2Fbuild%2Fvendor.js%3F9b2b4c4d081dd795c95280a4f7af90157c7ae917%3A130%3A359%0Ag%40http%3A%2F%2Flocalhost%3A9876%2Fbase%2Fbuild%2Fvendor.js%3F9b2b4c4d081dd795c95280a4f7af90157c7ae917%3A162%3A320%0Ahttp%3A%2F%2Flocalhost%3A9876%2Fbase%2Fbuild%2Fvendor.js%3F9b2b4c4d081dd795c95280a4f7af90157c7ae917%3A162%3A489%0Aq%40http%3A%2F%2Flocalhost%3A9876%2Fbase%2Fbuild%2Fvendor.js%3F9b2b4c4d081dd795c95280a4f7af90157c7ae917%3A130%3A359%0Ag%40http%3A%2F%2Flocalhost%3A9876%2Fbase%2Fbuild%2Fvendor.js%3F9b2b4c4d081dd795c95280a4f7af90157c7ae917%3A162%3A320%0Acb%40http%3A%2F%2Flocalhost%3A9876%2Fbase%2Fbuild%2Fvendor.js%3F9b2b4c4d081dd795c95280a4f7af90157c7ae917%3A166%3A337%0Ac%40http%3A%2F%2Flocalhost%3A9876%2Fbase%2Fbuild%2Fvendor.js%3F9b2b4c4d081dd795c95280a4f7af90157c7ae917%3A143%3A392%0ABc%40http%3A%2F%2Flocalhost%3A9876%2Fbase%2Fbuild%2Fvendor.js%3F9b2b4c4d081dd795c95280a4f7af90157c7ae917%3A144%3A180%0Ahttp%3A%2F%2Flocalhost%3A9876%2Fbase%2Fbuild%2Fapp.js%3F3b907e783e3f83ec76cf288ed98d542361f9ed3a%3A287%3A22%0Aj%40http%3A%2F%2Flocalhost%3A9876%2Fbase%2Fbuild%2Fvendor.js%3F9b2b4c4d081dd795c95280a4f7af90157c7ae917%3A2%3A26930%0AfireWith%40http%3A%2F%2Flocalhost%3A9876%2Fbase%2Fbuild%2Fvendor.js%3F9b2b4c4d081dd795c95280a4f7af90157c7ae917%3A2%3A27739%0Aready%40http%3A%2F%2Flocalhost%3A9876%2Fbase%2Fbuild%2Fvendor.js%3F9b2b4c4d081dd795c95280a4f7af90157c7ae917%3A2%3A29543%0AI%40http%3A%2F%2Flocalhost%3A9876%2Fbase%2Fbuild%2Fvendor.js%3F9b2b4c4d081dd795c95280a4f7af90157c7ae917%3A2%3A29728
at build/vendor.js:163
Chrome 52.0.2743 (Mac OS X 10.11.5): Executed 15 of 15 ERROR (0.187 secs / 0.123 secs)
Safari 9.1.1 (Mac OS X 10.11.5): Executed 15 of 15 ERROR (0.005 secs / 0.045 secs)


Any insight to what might be wrong here would be greatly appreciated.

Answer

Turns out, angular-cookies wasn't packed into vendor.js.

The conclusion revealed itself after we started testing with uncompressed code and we were able to get some readable error messages. From there it was quite obvious after a short time. The module wasn't loaded, because it wasn't available.

So we added

{"chunks": {
    ...
    "vendor":{
        ....
        "angular-cookies",
        ...
    }
  }
} 

and

 {"chunks": {
    ...
    "paths_uncompressed":{
        ....
         "angular-cookies": "bower_components/angular-cookies/angular-cookies.js"
        ...
    }
  }
} 

That was it. Thanks!

Comments