I am a new bee to Angular JS and was trying to make something out of it in a proper TDD way, but while testing i am getting this error:
Injector already created, can not register a module!
This is the service i am talking about.
bookCatalogApp.service('authorService', ["$resource", "$q", function($resource, $q){
var Author =$resource('/book-catalog/author/all',{},{
getAll : { method: 'GET', isArray: true}
});
var authorService = {};
authorService.assignAuthors = function(data){
authorService.allAuthors = data;
};
authorService.getAll = function(){
if (authorService.allAuthors)
return {then: function(callback){callback(authorService.allAuthors)}}
var deferred = $q.defer();
Author.getAll(function(data){
deferred.resolve(data);
authorService.assignAuthors(data);
});
return deferred.promise;
};
return authorService;
}]);
This is the test for the above service
describe("Author Book Service",function(){
var authorService;
beforeEach(module("bookCatalogApp"));
beforeEach(inject(function($injector) {
authorService = $injector.get('authorService');
}));
afterEach(function() {
httpBackend.verifyNoOutstandingExpectation();
httpBackend.verifyNoOutstandingRequest();
});
describe("#getAll", function() {
it('should get all the authors for the first time', function() {
var authors = [{id:1 , name:'Prayas'}, {id:2 , name:'Prateek'}];
httpBackend.when('GET', '/book-catalog/author/all').respond(200, authors);
var promise = authorService.getAll();
httpBackend.flush();
promise.then(function(data){
expect(data.length).toBe(2)
});
});
it('should get all the authors as they have already cached', function() {
authorService.allAuthors = [{id:1 , name:'Prayas'}, {id:2 , name:'Prateek'}];
var promise = authorService.getAll();
promise.then(function(data){
expect(data.length).toBe(2)
});
});
});
})
Any help will be appreciated.
If you are mixing calls to module('someApp') and inject($someDependency) you will get this error.
All your calls to module('someApp') must occur before your calls to inject($someDependency).
You're using the inject function wrong. As the documentation states, the inject function already instantiates a new instance of $injector. My guess is that by passing $injector as a argument to the inject function you are asking it to instantiate the $injector service twice.
Just use inject to pass in the service you want to check. Underneath the covers, inject will use the $injector service it instantiates to grab services.
You can fix this problem by changing the second beforeEach statement to:
beforeEach(inject(function(_authorService_) {
authorService = _authorService_;
}));
One other thing to note. The argument authorService passed to the inject function has been wrapped with '_' so it's name does not hide the variable created within the describe function. Thats also documented in the inject documentation.
Not sure that this is the cause, but your beforeEach should be like this:
beforeEach(function() {
inject(function($injector) {
authorService = $injector.get('authorService');
}
});
Related
I have a service as following.
InvService(...){
this.getROItems = function(cb){
$http.get('url').success(cb);
}
}
One of the controllers which uses the above:
var roItems = [];
InvService.getROItems(function(res){
roItems = res.lts.items;
});
In Jasmine, I want to test that roItems are assigned the values from the response. How can I achieve this?
I'd recommend that you have separated tests for you service and for your controller. If you want to test that roItems was assigned, you need to test your controller. Then, you could mock your service since it is not relevant for the controller test and make it return whatever you want. You need something like this:
describe('my awesome test', function() {
it('my awesome test block',
inject(function(InvService, $controller) {
//This mocks your service with a fake implementation.
//Note that I mocked before the controller initialization.
spyOn(InvService, 'getROItems').and.callFake(function(cb){
var resultFake = {
lts: {
items: "whatever you want"
}
}
cb(resultFake);
});
//This initializes your controller and it will use the mocked
//implementation of your service
var myController = $controller("myControllerName");
//Here we make the assertio
expect(myController.roItems).toBe("whatever you want");
}
)
});
Whenever, I am testing a controller and have something like this in it.
$scope.isSomething = function (Item) {
return ItemCollection.someItem(Item.attachedItem);
};
giving error on karma console:
TypeError: undefined is not an object (evaluating 'Item.attachedItem')
I am simply calling the function from the test file like this:
scope.isSomething();
I need to mock the Item.attachedItem or I am missing something here.. Please Explain in details as this is happening in multiple files.. thanks in advance
Also, for this type of code
.controller('itemCtrl', function (itemCollection) {
var vm = this;
this.itemCollection= itemCollection;
itemCollection.someItem().then(function (Item) {
vm.pageUrl = Item.pageUrl;
vm.Item= Item.someItems;
});
});
Also, this is also part of the code for more broad view here it gives Item.pageUrl is not a object error
Refer angular unit testing docs
The ItemCollection being a service, you could inject a mock while initialising a controller using
var ItemCollection, ItemCrtl;
beforeEach(inject(function($controller, $rootScope) {
$scope = $rootScope.$new();
ItemCollection = jasmine.createSpyObj('ItemCollection', ['someItem']);
ItemCrtl = $controller('ItemCtrl', {
$scope: scope,
ItemCollection: ItemCollection
});
});
For Item, the method isSomething should take care of checking if Item is undefined before doing Item.attachedItem
Testing an aync block is tricky. someItem returns a promise. $q an angular service to which can be used create async functions while testing.
We need to resolve the deferred object to test the async task.
var ItemCollection, ItemCrtl, deferedObj;
beforeEach(inject(function($controller, $rootScope, $q) {
$scope = $rootScope.$new();
deferedObj = $q.defer();
ItemCollection = jasmine.createSpyObj('ItemCollection', ['someItem']);
ItemCollection.someItem.andReturn(deferedObj.promise);
ItemCtrl = $controller('ItemCtrl', {
$scope: scope,
ItemCollection: ItemCollection
});
});
it('sets page url', function() {
deferedObj.resolve({ pageUrl: 'http://url', someItems: [1,2,3] });
scope.$apply();
expect(ItemCtrl.pageUrl).toEqual('http://url');
});
you have to use mock Item data in test like this (assuming attachedItem value is boolean)
var item={attachedItem:true}
scope.isSomething(item)
$scope.isSomething = function (Item) {
if(!Item.attachedItem){
Item.attachedItem=YOUR_MOCK_VALUE;
}
return ItemCollection.someItem(Item.attachedItem);
};
I'm trying to unit test a service that uses a repository which in turn returns a promise to the consumer.
I'm having trouble testing the promise, or I should say I don't know how test the promise.
Any help would be appreciated!
This is the test with $httpBackend and for mocking the service.
var describe = window.describe,
beforeEach = window.beforeEach,
afterEach = window.afterEach,
it = window.it,
expect = window.expect,
inject = window.inject,
module = window.module,
angular = window.angular,
serviceURL = '/' + Techsson.Core.Global.Language + '/api/sessionlimit/getdata',
$scope,
sessionLimitServiceResponse;
describe('Jasmine - SessionLimitService', function () {
beforeEach(module('sessionlimit.module'));
var sessionLimitServiceMock, q;
beforeEach(inject(function (_SessionLimitService_, _SessionLimitResository_, $httpBackend, $rootScope) {
sessionLimitServiceMock = _SessionLimitService_;
//remove the use of global variables
$httpBackend.when('GET', serviceURL)
.respond('foo', {/*Headers*/});
}));
it("Content array must be empty", function () {
expect(sessionLimitServiceMock.content.length).toEqual(0);
});
it('Content array must have a value', function() {
$httpBackend.expectGET(serviceURL);
sessionLimitServiceMock.getData().then(function(value) {
expect(value).toEqual('foo'); // NOTHING HAPPENS
});
$httpBackend.flush();
});
});
I have a service that is making an AJAX request to the backend
Service:
function GetCompaniesService(options)
{
this.url = '/company';
this.Companies = undefined;
this.CompaniesPromise = $http.get(this.url);
}
Controller:
var CompaniesOb = new GetCompanies();
CompaniesOb.CompaniesPromise.then(function(data){
$scope.Companies = data;
});
I want my service to handle the ".then" function instead of having to handle it in my controller, and I want to be able to have my controller act on that data FROM the service, after the promise inside the service has been resolved.
Basically, I want to be able to access the data like so:
var CompaniesOb = new GetCompanies();
$scope.Companies = CompaniesOb.Companies;
With the resolution of the promise being handled inside of the service itself.
Is this possible? Or is the only way that I can access that promise's resolution is from outside the service?
If all you want is to handle the response of $http in the service itself, you can add a then function to the service where you do more processing then return from that then function, like this:
function GetCompaniesService(options) {
this.url = '/company';
this.Companies = undefined;
this.CompaniesPromise = $http.get(this.url).then(function(response) {
/* handle response then */
return response
})
}
But you'll still have use a promise in the controller, but what you get back will have already been handled in the service.
var CompaniesOb = new GetCompanies();
CompaniesOb.CompaniesPromise.then(function(dataAlreadyHandledInService) {
$scope.Companies = dataAlreadyHandledInService;
});
There is no problem to achieve that!
The main thing you have to keep in mind is that you have to keep the same object reference (and in javascript arrays are objects) in your service.
here is our simple HTML:
<div ng-controller = "companiesCtrl">
<ul ng-repeat="company in companies">
<li>{{company}}</li>
</ul>
</div>
Here is our service implementation:
serviceDataCaching.service('companiesSrv', ['$timeout', function($timeout){
var self = this;
var httpResult = [
'company 1',
'company 2',
'company 3'
];
this.companies = ['preloaded company'];
this.getCompanies = function() {
// we simulate an async operation
return $timeout(function(){
// keep the array object reference!!
self.companies.splice(0, self.companies.length);
// if you use the following code:
// self.companies = [];
// the controller will loose the reference to the array object as we are creating an new one
// as a result it will no longer get the changes made here!
for(var i=0; i< httpResult.length; i++){
self.companies.push(httpResult[i]);
}
return self.companies;
}, 3000);
}}]);
And finally the controller as you wanted it:
serviceDataCaching.controller('companiesCtrl', function ($scope, companiesSrv) {
$scope.companies = companiesSrv.companies;
companiesSrv.getCompanies();
});
Explanations
As said above, the trick is to keep the reference between the service and the controller. Once you respect this, you can totally bind your controller scope on a public property of your service.
Here a fiddle that wraps it up.
In the comments of the code you can try uncomment the piece that does not work and you will see how the controller is loosing the reference. In fact the controller will keep having a reference to the old array while the service will change the new one.
One last important thing: keep in mind that the $timeout is triggering a $apply() on the rootSCope. This is why our controller scope is refreshing 'alone'. Without it, and if you try to replace it with a normal setTimeout() you will see that the controller is not updating the company list.
To work around this you can:
don't do anything if your data is fetched with $http as it calls a $apply on success
wrap you result in a $timeout(..., 0);
inject $rootSCope in the service and call $apply() on it when the asynchronous operation is done
in the controller add a $scope.$apply() on the getCompanies() promise success
Hope this helps!
You can pass the $scope into GetCompanies and set $scope.Companies to the data in the service
function GetCompaniesService(options,scope)
{
this.url = '/company';
this.Companies = undefined;
this.CompaniesPromise = $http.get(this.url).then(function(res) {
scope.Companies = res;
});
}
You have to be careful about the order in which you then use the data. That's kind of the reason behind a promise to begin with.
In my app i am writing a decorator for $log so that i can customize the functionality of $log including calling a third party service. The third party service injects $q for its internal activities. Now this causes circular dependency error :
Uncaught Error: Circular dependency: $q <- tploggerService <- $log <- $exceptionHandler <- $rootScope.
Because qProvider uses exceptionHandlerProvider which ultimately uses logProvider which i guess is causing this. Has any one faced similar issues while decorating and is there a solution to work around this or a different pattern to work around the problem?
Here is a simple demonstration of code, Appreciate your help:
///Some third party APP
angular.module('SomeThirdPartyApp', []);
tploggerService.$inject = ['$q']; //<-- $q is a dependency
function tploggerService ($q) {
this.info = function (data) {
var deferred = $q.defer(); //Doing something...
//....
//....
};
}
angular.module('SomeThirdPartyApp').service('tploggerService', tploggerService);
///--------------------------------------------------------------------------------
///MY APP
angular.module('decorApp', ['SomeThirdPartyApp']);
angular.module('decorApp').config([
'$provide', function ($provide) {
$provide.decorator('$log', ['tploggerService','$delegate',
function (tploggerService, $delegate) { //<--- Injecting tpLoggerService causes circular dependency error.
var _info = $delegate.info;
//It is no different even if we use $injector
$delegate.info = function(){
var args; //doing something with the arguments basically formatting massaging it.
var customlogmessage; //doing something with args
tploggerService.info(customlogmessage);
_info.apply(null, args);
}
return $delegate;
}]);
}]);
JSBIN
Plnkr after $q removed
Get $q from inside of your service:
function tploggerService ($injector) {
var $q;
this.info = function (data) {
$q = $injector.get('$q');
var deferred = $q.defer(); //Yes using defered object. some this performs some actions and some internal stuffs.
//Doing something...
};
}
Updated Plunk
It seems that you can only $delegate in the $provider.decorator(), I think if you should put the bussiness logic in the decorator function other than using an inject way.For example:
$provider.decorator('$log',function($delegate){
$delegate.info = function(){
var args = Array.prototype.slice(arguments);
var deferred = $q.defer(); //Doing something...
$delegate.info.apply(null,args);
}
});