I am fairly new to AngularJS and am trying to learn some best practices. I have things working, but would like to start adding some unit tests to my modules and controllers. The first one I am looking to tackle is my AuthModule.
I have an AuthModule. This Module registers a Factory called "AuthModule" and exposes things like "setAuthenticatedUser" and also fields like "isLoggedIn" and "currentUser". I think this is a fairly common pattern in an AngularJS application, with some variations on the specific implementation details.
authModule.factory(
'AuthModule',
function(APIService, $rootScope) {
var _currentUser = null;
var _isLoggedIn = false;
return {
'setAuthenticatedUser' : function(currentUser) {
_currentUser = currentUser;
_isLoggedIn = currentUser == null ? false : true;
$rootScope.$broadcast('event:authenticatedUserChanged',
_currentUser);
if (_isLoggedIn == false) {
$rootScope.$broadcast('event:loginRequired')
}
$rootScope.authenticatedUser = _currentUser;
$rootScope.isLoggedIn = _isLoggedIn;
},
'isLoggedIn' : _isLoggedIn,
'currentUser' : _currentUser
}
});
The module does some other things like register a handler for the event "loginRequired" to send the person back to the home screen. These events are raised by the AuthModule factory.
authModule.run(function($rootScope, $log, $location) {
$rootScope.$on("event:loginRequired", function(event, data) {
$log.info("sending him home. Login is required");
$location.path("/");
});
});
Finally, the module has a run block which will use an API service I have to determine the current logged in user form the backend.
authModule.run(
function(APIService, $log, AuthModule) {
APIService.keepAlive().then(function(currentUser) {
AuthModule.setAuthenticatedUser(currentUser.user);
}, function(response) {
AuthModule.setAuthenticatedUser(null);
});
});
Here are some of my questions:
My question is how would you setup tests for this? I would think that I would need to Mock out the APIService? I'm having a hard time because I keep getting unexpected POST request to my /keepalive function (called within APIService.keepAlive())?
Is there any way to use $httpBackend in order to return the right response to the actual KeepAlive call? This would prevent me from having to mock-out the API service?
Should I pull the .run() block out which obtains the current logged in user out of the AuthModule and put it into the main application? It seems no matter where I put the run() block, I can't seem to initialize the $httpbackend before I load the module?
Should the AuthModule even be its own module at all? or should I just use the main application module and register the factory there?
Run blocks are the closest thing in Angular to the main method. A run block is the code which needs to run to kickstart the application. It is executed after all of the service have been configured and the injector has been created. Run blocks typically contain code which is hard to unit-test, and for this reason should be declared in isolated modules, so that they can be ignored in the unit-tests.angularjs docs
I suggest you take a look at this authentication service, using a service is the way to go.
Hopefully this would help ... Good luck
Related
I have an extremely edge case scenario where I have a callback method I have to define during config. Which means no scope, no factories, etc... My work around is to use the root injector ($injector) and get my other modules at runtime.
However, when I call $injector.get('myServiceName') in my call back (after the application is running) I get "unknown provider". The same service has no problem (and actually is) being injected into a before my line of code is running. If I call $injector.get("myServiceNameProvider") then I can get the provider back in my callback.. But another service that only has a factory I can't get at all.
So in this extremely bad practice, how can I snag the service I configured. Heck I can't even seem to get $rootScope..
Angular inits providers first, then factories/services - services are not available in app.config.
You can deo like this:
app.config(...
window.on('scroll', function() {
// this is executed later
$injector.get()...
})
Or use app.run where services are available.
I think I had similar problem few months ago... I need to construct breadcrumbs, but they had to be created in config phase (ui-router). I have done it like this (breadcrumbs-generation-service.provider.js):
var somethingYouHaveToHaveLater = undefined;
function BreadcrumbsGenerationService () {
this.createStateData = createStateData;
function createStateData (arg) {
somethingYouHaveToHaveLater = arg;
};
}
function BreadcrumbsGenerationServiceProvider () {
this.$get = function BreadcrumbsGenerationServiceFactory () {
return new BreadcrumbsGenerationService();
}
}
angular
.module('ncAdminApp')
.provider('BreadcrumbsGenerationService', BreadcrumbsGenerationServiceProvider);
Because service is used inside Angular configs, needs to be injected as provider to be available in config phase: Similar SO Question. Despite the fact is registered as BreadcrumbsGenerationService needs to be injected as BreadcrumbsGenerationServiceProvider to config phase and used with $get():
BreadcrumbsGenerationServiceProvider.$get().createStateData(someParams);
But in controller, inject it without Provider suffix (BreadcrumbsGenerationServic) and it behaves as normal service.
I'm using Angular to develop commenting functionality for a web app.
Currently there are two sections in the application were a user can comment:
Category
Product
About 90% of the commenting functionality is the same for both sections and as such I would like to make this reusable - i.e write some service or controller that I can reference/use as a base.
So far, my research seems to point to using a factory service but unfortunately this doesn't seem to work (I've spent the whole day running through various tutorials).
It is quite possible that I am over thinking this and making it far too complicated but I honestly don't know which way to turn anymore.
Herewith a quick and dirty overview of what I have so far:
HTML view for the category
Controller for the category (receives data from service and posts data to service in order to bind data to model)
Service for the category (retrieve and stores all the necessary
data)
The product uses the same logic and a lot of the code in the service and controller will be duplicated.
I've merged the two services into one service successfully but I'm having trouble doing the same for the controller.
Do I:
Write a base controller that will communicate with the above mentioned service and that will hookup with the two existing controllers
OR
Write a factory/provider service that hooks up to the two existing controllers as well as the above mentioned service.
If you go the route of using a factory, you could put all the common functionality into its return object and reference that from your controllers.
Factory
angular.module('myModule').factory('CommonFunctions', function(){
return {
foo : foo,
bar : bar
}
function foo(){
console.log('foo');
};
function bar (){
console.log('bar');
};
}
Controller
angular.module('myModule')
.controller('myController', ['CommonFunctions', function(CommonFunctions) {
var vm = this;
vm.foo = CommonFunctions.foo();
vm.bar = CommonFunctions.bar();
}
angular's separation of service types ie:
for specific values
constant
value
(constant for specific values needed before other services are created)
for functions
factory
service
provider
(provider for specific instances when you need a services before other services are created, usually taking advantage of constants)
allow the ability to share data and ways to process that data between controllers and or directives, anything that can be a value can also be a constant, the only difference there being where they can be injected. Similarly any service can be rewritten to a factory or a provider, it is more your specific use case / what your more comfortable writing that would determine which to use, but really the best way to think about it is if you have a value that needs to be shared but is not needed inside angular.module.config then use value, otherwise use constant, now if you have a single function that you want to share, (maybe it processes that value in some way or maybe it just does something else) you should write it as a factory, then when you have a few of those factory's that deal with either that value, or anything else, you can combine them into a service or configure and combine them using a provider. here is a simple example (note i am using the recommended syntax for writing angular services):
'use strict';
var app = angular.module('test.app',[]);
app.constant('configureableValue',{defaultValue:55});
app.value('editableValue',{defaultValue:100,editedValue:null});
app.provider('configureValue',configureValueProvider);
configureValueProvider.$inject - ['configureableValue'];
function configureValueProvider(configureableValue){
var defaultVal = configureableValue.defaultValue,
originalVal = defaultVal;
return {
getValue:getValue,
setValue:setValue,
resetValue:resetValue,
'$get':providerFunc
};
function getValue(){
return defaultVal;
}
function setValue(val){
defaultVal = val;
}
function providerFunc(){
return {
get:function(){ return getValue(); },
reset:function(){ resetValue(); }
};
}
function resetValue(){
defaultVal = originalVal
}
}
// this factory is an example of a single function service, this should almost always be defined as a factory
app.factory('getEditableValue',getEditableValue);
getEditableValue.$inject = ['editableValue'];
function getEditableValue(editableValue){
return function(){
return editableValue.editedValue ? editableValue.editedValue : editableValue.defaultValue;
};
}
// same with this one
app.factory('setEditableValue',setEditableValue);
setEditableValue.$inject = ['editableValue'];
function setEditableValue(editableValue){
return function(val){
editableValue.editedValue = val;
}
}
// now this is an example of a service service collecting the factorys for an object with all the related behavior we need
app.service('editableService',editableService);
editableService.$inject = ['getEditableValue','setEditableValue'];
function editableService(getEditableValue,setEditableValue){
var self = this;
self.setVal = setEditableValue;
self.getVal = getEditableValue;
}
app.config(appConfig);
appConfig.$inject = ['configureValueProvider'];
function appConfig(configureValueProvider){
configureValueProvider.setValue('i changed '+ configureValueProvider.getValue() +' to this!!!!');
}
app.run(appRun);
appRun.$inject = ['configureValue','editableService'];
function appRun(configureValue,editableService){
console.log('before editing: ',editableService.getVal());
editableService.setVal('changed!!!');
console.log('after editing: ',editableService.getVal());
console.log('we changed this in the config func: ',configureValue.get());
configureValue.reset();
console.log('and now its back to the original value: ',configureValue.get());
}
i know thats a lot for a simple example, but there are a lot of features provided by angular, and many ways to use them, hopefully this helps.
I am writing end-to-end tests for my AngularJS-based application using Protractor. Some cases require using mocks to test - for example, a network connection issue. If an AJAX request to server fails, the user must see a warning message.
My mocks are registered in the application as services. I want them to be accessible to the tests to write something like this:
var proxy;
beforeEach(function() { proxy = getProxyMock(); });
it("When network is OK, request succeeds", function(done) {
proxy.networkAvailable = true;
element(by.id('loginButton')).click().then(function() {
expect(element(by.id('error')).count()).toEqual(0);
done();
});
});
it("When network is faulty, message is displayed", function(done) {
proxy.networkAvailable = false;
element(by.id('loginButton')).click().then(function() {
expect(element(by.id('error')).count()).toEqual(1);
done();
});
});
How do I implement the getProxyMock function to pass an object from the application to the test? I can store proxies in the window object of the app, but still do not know how to access it.
After some reading and understanding the testing process a bit better, it turned to be impossible. The tests are executed in NodeJS, and the frontend code in a browser - Javascript object instances cannot be truly shared between two different processes.
However, there is a workaround: you can execute a script inside browser.
First, your frontend code must provide some sort of service locator, like this:
angular.module('myModule', [])
.service('proxy', NetworkProxy)
.run(function(proxy) {
window.MY_SERVICES = {
proxy: proxy,
};
});
Then, the test goes like this:
it("Testing the script", function(done) {
browser.executeScript(function() {
window.MY_SERVICES.proxy.networkAvailable = false;
});
element(by.id('loginButton')).click().then(function() {
expect(element.all(by.id('error')).count()).toEqual(1);
done();
});
});
Please note that when you use executeScript, the function is serialized to be sent to browser for execution. This puts some limitations worth keeping in mind: if your script function returns a value, it is a clone of the original object from browser. Updating the returned value will not modify the original! For the same reason, you cannot use closures in the function.
I’m building an app for 1 admin to be able to log in and edit content. When logged in the app will serve up a private html doc structure instead of those not logged in being presented the normal public docs. I’m using Angular-route, Session and Passport for authentication. I want to check if a user is logged in whenever they link to another page on the site. So, if they go somewhere they’re not authorized to be, they can’t edit content on the site.
I’m getting an $injector:modulerr when I run the app on a localhost. The ReferenceError says checkAuth is not defined.
I tried creating a global variable using $rootScope as suggested here:
app.run(['$rootScope', '$http', function($rootScope, $http){
$rootScope.checkAuth = function(){
console.log("rootScope things are happening");
$http.get('/login/loggedin').success(function(user){
if (user !== '0') {
console.log('Authenticated');
return true;
} else {
console.log('Not Authenticated');
return false;
}
});
};
}]);
So, if the user’s logged in, I should get a true and if they’re not logged in, I’ll get a false. But, I still get the checkAuth is not defined injector error and I never see any of my console logs.
The Angular documentation for $rootScope says to use this format:
$rootScope.Scope([providers], [instanceCache]);
So, I tried this:
$rootScope.Scope(['$rootScope', '$http', function($rootScope, $http){...
Unfortunately, that doesn’t work either.
This is an example of using Passport and Angular together which I found somewhat helpful, but they’re allowing multiple users to register (which I don’t want) and it still doesn’t solve the question of a global function for checking if a user is logged in across multiple pages in a website. It may help you to see a similar controller and route file though.
I know that you can use factories and services to do something similar as suggested in previous similar questions, but I was hoping to go this route before trying a different path. Obviously though, if that's the best way, I'll give it a go.
The provided rootscope declaration seems ok to me.
Assign a rootscope value.
app.run(function($rootScope) {
$rootScope.checkAuth = function(){
$http.get('/login/loggedin').success(function(user){
if (user !== '0') {
return true;
} else {
return false;
}
})
};
});
Use it like (don't forget $rootscope dependancy)
app.controller('myCtrl', function($scope, $rootScope) {
$scope.authenticated = function() {
return $rootScope.checkAuth ;
};
});
See live sample to compare: http://jsfiddle.net/TmPk5/6/
In my Angular app, I have some resource modules, each containing some cache factories.
For example,
projectRsrc.factory('getProjectCache', ['$cacheFactory', function($cacheFactory){
return $cacheFactory('getProjectCache');
}]);
I have a few of these to cache values received from the servers.
The problem is that at times I'd like to clear all the caches. So I want to put all the cacheFactories into one CacheCentralApp module and delete all the caches with a single call.
The trouble is, I don't know of any way to access other factories inside my module. So for instance, if I create a module CacheCentralApp, and in it, declare factories that provide cacheFactorys, how can I create a function in there that calls removeAll() on every cacheFactory?
I don't think it is possible to target all the factories of a certain module. I think however that another solution to your problem is to send a event that all factories has to be cleared. This will prevent that you will have to loop through all your factories and call a .clear() function on everyone.
You could send a event request with the following code:
$scope.$broadcast('clearAllCaches');
And listen to this event in every factory with:
$scope.$on('clearAllCaches', function() {
clearCache();
}
In a separate module you might create a factory for that:
var cacheModule = angular.module('CacheCentralApp', []);
cacheModule.factory('MyCacheFactory', ['$cacheFactory', function($cacheFactory) {
var cacheKeys = [];
return {
clearAll: function() {
angular.forEach(cacheKeys, function(key) {
$cacheFactory.get(key).removeAll();
});
},
get: function(key) {
if(cacheKeys.indexOf(key) == -1) {
cacheKeys.push(key);
return $cacheFactory(key);
} else {
return $cacheFactory.get(key);
}
}
}
}]);
To create new or get existing Cache you simply call MyCacheFactory.get(cacheName). To clear all the caches ever created in the factory you call MyCacheFactory.clearAll().
Note: I am not quite sure that Array.indexOf is available in every browser, you might want to use Lo-Dash or another library to make sure your code works.