-I've seen many questions around testing and DI, but none that has helped me so far.
I have a module:
angular
.module('app.users', ['app.auth', 'app.foo']);
and simple service with a function I want to test:
(function(){
angular
.module('app.users')
.factory('UsersModel', UsersModel);
UsersModel.$inject = ["AuthService", "FooService"]
function UsersModel(AuthService, FooService){
function someFunction(){
// do stuff...
}
}
})();
And a test file:
describe("Users", function(){
beforeEach(function(){
module('app.users');
angular.mock.module('app.auth'); // includes AuthService
angular.mock.module('app.foo'); // include FooService
})
describe("Username", function(){
it("should do stuff", inject(function(UsersModel){
// no code here
}));
});
});
Now, the service FooService itself depends on BarService. When I run this in karma, I get an error:
Unknown provider: BarServiceProvider <- BarService <- FooService <- UsersModel
And this confuses me: since I use angular.mock.module('app.foo'), I would have expected the dependencies of fooService to not even be on the radar. Apparently they are though, so I'm thinking that I'm doing something wrong.
How should I go about dealing with my service's dependencies? Should I stub FooService?
Related
I'm trying to write unit tests for an angular service with jasmine/karma. I have a similar service test, which works just fine. But this one has some additional dependencies, is in a different module and just doesn't find the service with the inject.
The service looks something like this. bService is in the same module, but commonFactory and commonService are in another module, say commonModule.
(function () {
'use strict';
angular
.module('myService')
.service('aService', aService);
aService.$inject = [
'commonFactory',
'commonService'
'bService'
];
function aService (
commonFactory,
commonService,
bService
) {
};
return {
codeIWantToTest: CodeIWantToTest;
}
function CodeIWantToTest () {
console.log('surprise!');
}
})();
My jasmine test looks like:
describe('myService.aService', function () {
'use strict';
var aService;
// I tried adding beforeEach(module('commonModule')); too, but that didn't do anything
beforeEach(module('myService'));
beforeEach(function() {
inject(function(_aService_) {
console.log('getting aService');
aService = _aService_;
});
});
it('tests my service is defined', function() {
expect(myService).toBeDefined();
});
});
This test fails. myService isn't defined and the console.log in the inject function doesn't ever fire. My karma.conf.js basically lists the dependencies in the order that they're injected into the service, then adds the service then the test.
What would cause the inject to not grab the service? What am I missing? I mentioned I have a similar test for commonService and it works just fine. So I'm baffled.
Another dev on my team found the solution and I wanted to post it as an answer for the future people. I had a feeling it was a dependency problem, and it was. While we were loading all of the JS stuff correctly, the template that the component uses was loading another js dependency. So to fix this for jasmine, we had two different solutions:
at the top of the component test file, we could add:
beforeEach(function () {
module(function ($provide) {
$provide.constant('myMissingDependency', {
// do things to load the dependency here
});
});
});
In our case it was a translation library
The other solution was to add a 'shim' file into the unit test directory and load it with karma.config.js ahead of the tests. That looked like:
(function() {
angular
.module('MyService')
.constant('myMissingDependency', Object.freeze({
// things to load the dependency
}));
})();
I wasn't able to switch to Chrome because we're using Docker and I couldn't get the tests to run locally to run Chrome. So adding a second set of eyes to this was what I needed.
I have the following code in my spec file
beforeEach(function () {
module('app');
inject(function ($injector) {
user = $injector.get('app.user');
});
});
user is undefined, and isn't being injected. So I want to make sure that the app module actually loaded.
If the module is not loaded, you get $injector:nomod error. If the module is loaded but the service cannot be found, you get $injector:unpr error. It is as easy as that. There is always a breadcrumb trail, no need to probe Angular to know if it fails silently or not.
Just make sure you're using the right module name. You can use beforeEach to load your module. Also, with $injector you can get an instance of your service or controller you're trying to test:
'use strict';
describe('MyControllerName', function () {
var MyControllerName;
beforeEach(module('myAppMomduleName'));
beforeEach(inject(function ($injector) {
MyControllerName = $injector.get('MyControllerName');
}));
it('should create an instance of the controller', function () {
expect(MyControllerName).toBeDefined();
});
});
I am trying to mock a factory within one of my angularjs modules. The full angularjs application is
angular.module('administration', ['administrationServices'])
The dependency:
var module = angular.module('administrationServices')
contains a factory service:
module.factory('Example', [function(){
return{
list:function(){
return ['This is real']
}
}
}])
This is the service I am attempting to override in my protractor e2e test. The actual test looks something like this:
describe('Example page', function(){
beforeEach(function() {
var mock = function(){
// get the module and provide the mock
var module = angular.module('administrationServices').config(['$provide', function($provide){
$provide.factory('Example',[function(){
return {
list: function(){
return ['This is a Mock Test']
}
}
}])
}])
}
// override the mock service
browser.addMockModule('administrationServices', mock)
})
it('should go to the page and see mock text', function() {
// code that goes to page and checks if the service works
// ...
})
})
The issue I'm having occurs when I $ protractor conf.js, I get the error:
Error while running module script administrationServices: [$injector:nomod] http://errors.angularjs.org/1.3.4/$injector/nomod?p0=administrationServices
This is where I'm confused. Every blog post about protractor's addMockModule uses similar syntax it seems. There are other factory services in administrationServices and those seem to get overwritten because the app can't open to the page due to those services (user and group services) not working.
Anyways, if somebody has seen this and can help direct me in the right direction, that would help; I am fairly new to mock services, protractor and e2e testing.
I think the problem is that your mock function does not return anything. It doesn't share the new module outside the function scope.
var mock = function(){
// get the module and provide the mock
return angular.module('administrationServices').config(['$provide', function($provide){
$provide.factory('Example',[function(){
return {
list: function(){
return ['This is a Mock Test'];
}
}
}])
}])
};
// override the mock service
browser.addMockModule('administrationServices', mock)
Just make it return the new module and it should be fine.
I'm new to angular and unit-testing.
I have an application module MyApp including basic things an services, that are needed in all other modules, like service for logging loggingService
I also have an module for handling everything about map&geo-positon, called MapModule and I have an main module for application logic, called MainModule
The MainModule contains a controller, that I like to test: messageSendCtrl
The controller has some dependencies, like services from MapModule.
And: MainModule and MapModule has dependencies to the MyApp, because the loggingServiceis needed everywhere.
The code looks like that (pseudo-code):
MyApp
var MyApp = angular
.module('MyApp', ['ngRoute','MainModule','MapModule']);
MyApp.service('loggingService', function (one, two) {
[..] /*logging data somewhere for debugging application*/
});
MainModule
var MainModule = angular
.module('MainModule', []);
MainModule.controller('messageSendCtrl',
function($scope,$http, $location, locationService, loggingService) {
[...]
});
MapModule
var MapModule = angular
.module('MapModule', ['uiGmapgoogle-maps']);
MapModule.service('locationService', function (loggingService) {
[...]
What I like to test is the messageSendCtrl from the MainModule. (probably) I was able to inject the location service into the test environment. But injecting the locationService was not successful.
Probably because locationService also uses the loggingService.
Running the test results in
Error: [$injector:unpr] Unknown provider: loggingServiceProvider <- loggingService <- locationService
My test looks like that:
describe('saving a document', function() {
beforeEach(module('MainModule'));
beforeEach(module('MyApp'));
beforeEach(module('MapModule'));
describe ('messageSendCtrl', function () {
var scope,ctrl,locationService,loggingService;
beforeEach(inject(function($rootScope, $controller,_locationService_,_loggingService_) {
scope = $rootScope.$new();
ctrl = $controller('messageSendCtrl',
{$scope: scope,
locationService: _locationService_,
loggingService : _loggingService_ });
}));
it('should actual not saved', function(){
expect(scope.result).to.equal('unsaved');
});
})
});
So who can I solve the dependencies? Or is there an design problem at my application?
there are multiple things going on, let's check it one by one:
at your test, you don't need do load all your modules, load just that module, that you want to test, your ctrl is in your MainModule, so use just
beforeEach(module('MainModule'));
every module should declare its dependencies, so your MainModule declaration should look like this: var MainModule = angular.module('MainModule', ['MyApp']); because one of your controller in your MainModule dependent on a service that is in an other module (MyApp)
it is easier to test if one module do just one thing, so if you have a logging service, make a logging service module for that, and include that module where you want to use logging.
So don't make modules that is responsible for several different things, because if an other module need logging, that module will get every other service that your "godmodule" contains, and that makes difficult to test, and find bugs.
I'm a total noob to unit testing javaScript, angularJS, and Karma. I've been able to write passing tests for the controllers, but now that I'm attempting to test the services, I'm getting the following error: Unknown provider <- nProvider <- User where User is the name of my service and from what I can tell, "n" is the $http dependency for that service (minified). Here is my UserService (TypeScript):
/// <reference path="../../lib/angular/angular.d.ts" />
module MyApp.Services{
export class UserService{
private _apiBase: string = "api/";
constructor(private $http: ng.IHttpService, private $q: ng.IQService) {
}
getUser(userSearchParams: string): ng.IPromise{
var deferred = this.$q.defer();
this.$http.get(this._apiBase + "Users/" + userSearchParams).success((data) => {
deferred.resolve(data);
}).error(() => {
deferred.reject();
});
return deferred.promise;
}
}
}
and here is my generic Services.ts file:
/// <reference path="../_all.d.ts" />
'use strict'
var mod = angular.module('MyApp.Services', []);
mod.factory('User', [<any>"$http", <any>"$q", function ($http, $q) {
return new MyApp.Services.UserService($http, $q);
}]);
and finally, my test script:
'use strict';
describe('UserServiceTests', function () {
beforeEach(module('main')); //this is my main angular module - set up in app.ts
it('should issue a GET request to api/Users/whatever when the search parameter is whatever', inject(function(User) {
expect(2).toBe(2);
}));
})
I've created another test app with the same structure and I don't get the error seen above. I don't know what I'm missing. Let me know if there is anything more that I need to post in order to find the problem. Any help is appreciated.
Edit: After much searching and reading, it appears that this is related to dependency injection here and here. Upon minification, the parameter names get changed, thus causing the error. As proof, if I delete the min files and re-run the tests, they pass.
The $inject function can be used to fix this, but I don't see how to do it when the controller is created as a class using TypeScript. I'm not sure if I can use the resolve property of $routeProvider.when.
Ok, I found this post, which allowed me to resolve the issue. By adding an "inject" property to my TypeScript classes (with the appropriate values in the array), Angular knows how to handle the DI. The only drawback I see is the potential for error in the order the dependencies are listed, or omission in the event a new dependency is added at a later time.