how to not duplicate httpBackend code in angular tests? - angularjs

I have some services that call for data while loading.
so my tests fail because there are unexpected calls that I need to specify in $httpBackend.
this causes a lot of duplicate code in my tests.
part of my attempts to reduce the duplicated code, I decided to add $rootScope.test flag, and if this flag is on those services do not load the data but still I need to duplicate $rootScope.test=true all over the tests.
Is there a way to do this properly in angular tests?
Here is some code
$httpBackend.expectGET('/backend/system/translations/en.json').respond({'angularjs': 'cool'});
$httpBackend.expectGET('/backend/system/translations/he.json').respond({'angularjs': 'cool'});
$httpBackend.expectGET('/backend/system/translations/ru.json').respond({'angularjs': 'cool'});
$httpBackend.expectGET('/backend/system/translations/ar.json').respond({'angularjs': 'cool'});
$httpBackend.expectGET('/translations/general.json').respond({'angularjs': 'cool'});
i18n = $filter('i18n');
Every directive I have with some translation support require these statements per language.

To handle every url in the same way with just one statement try sth like this:
$httpBackend.expectGET(function(url){
return true;
}).respond({'angularjs': 'cool'});
From documentation first argument of expectGET is:
HTTP url or function that receives the url and returns true if the url
match the current definition.
If you want to define response to just translation urls, try sth like this:
$httpBackend.expectGET(function(url){
return url.lastIndexOf("/backend/system/translations/", 0) === 0;
}).respond({'angularjs': 'cool'});
Also, are you sure that you need to use expectGET() instead of whenGET()? If you don't care whether given urls were called or not, how many times and in which order and you just want to define responses, then when... methods are the way to go:
See: "Request Expectations vs Backend Definitions" section of the mentioned doc page.
Backend definitions allow you to define a fake backend for your
application which doesn't assert if a particular request was made or
not, it just returns a trained response if a request is made. The test
will pass whether or not the request gets made during testing.

Related

angularjs, ngResource in a factory and network calls

Trying to get my head around this. I have a simple factory using ngResouce, like this:
.factory('FooResource', function($resource) {
var foo = $resource('/api/foo').get();
return foo;
})
And in my app, in multiple places, in multiple controllers over time, I use the value of 'FooResource.bar' (where 'bar' is returned in the data from the get() call).
Is it true that the network call to '/api/foo' will only happen on the first reference for the life of my SPA? Does that first reference need to 'FooResource.bar' be handled like a promise?
From what I see in my playing around with code, it seems like the first question is 'yes' and the second is 'no', but don't know if that's really true in general, or just happening because its a small test app on my dev box.
Edit: I guess part of what I want validation on is my thinking that since this is in a factory, which is a singelton, the $resource call will only ever be made once. Is that true?
Depends, and yes. You will always need to handle it as a promise, and you can enable/disable the http cache. If you have the cache set to true, then the request will send off once and be cached until the cache is cleared.
You can find more about the $resource caching in the $resource documentation here: https://docs.angularjs.org/api/ngResource/service/$resource

Call translation service from a callback registered in an app.config section

I'm relatively new to AngularJS and the problem I'm facing is one of those "I want to inject a Service into an app.config" type of scenarios, which I realise cannot be done. (I'm comfortable with the different between Service and Provider, and why a Service cannot be injected into a .config.)
What I am trying to accomplish is to use angular-schema-form together with angular-translate such that field titles in generated forms are translated.
There is an issue where someone asks how to do this, and the advice given is to take advantage of angular-schema-form's postProcess, which is a property of the Provider. This callback gives you the form object before it is rendered, giving you the opportunity to manipulate it with user code. Therefore translation could be done within here.
The postProcess method is called on the Provider, so it is done within an app.config:
app.config(function(schemaFormProvider, $translateProvider) {
schemaFormProvider.postProcess(function(form){
// within here I can inspect the form object, find all
// properties whose key is "title", and then perform
// language translation on their values.
So, that is apparently the place where I have an opportunity to manipulate control titles and so on.
Over to the angular-translate library, for me to 'manually' translate strings, I can use the $translate service. This provides both synchronous and asynchronous methods to translate a given key string. The synchronous one is $translate.instant(key).
To glue these two together, what I have tried so far (which does work) is to create a 'bridge' method like this:
var app = angular.module('myApplicationName', ['schemaForm', 'pascalprecht.translate']);
....
app.config(function(schemaFormProvider, $translateProvider) {
schemaFormProvider.postProcess(function(form){
// ... code here which iterates over properties
// and finds all control titles ...
key = app.myTranslate(key);
// ....
}
....
});
app.myTranslate = function (key) {
var service = angular.injector(['ng', 'myApplicationName']).get("$translate");
return service.instant(key);
}
This does work, but it seems ugly and unsafe (as presumably there's no guarantee $translate is ready when the callback is first invoked) and the calls to angular.injector(['ng', 'myApplicationName']).get... are presumably expensive.
Is there a better way, or is this the only way I'm going to get it done, considering the constraints of the libraries I'm working with?
I have also considered an alternative approach altogether, which would be to instead perform the translations on the schema or form objects before they are processed by angular-schema-form. This could be done from within Controllers, eliminating the problem of accessing the $translate service. I may end up going down that route, but it would still be nice to understand the best solution for the above scenario.

How to test actions in Flux/React?

I'm trying to figure out how to test actions in flux. Stores are simple enough with the provided example, but there seems to be nothing out there for the actions/data/api layer.
In my particular app, I need to pre-process something before posting it to my server. Based on the advice in this post, I decided to implement the async stuff in my actions. What I can't figure out is how to test this preprocessing.
For example in MissionActions.js:
addMissionFromBank: function(bankMission) {
var mission = new Mission({game: GameStore.getGame().resource_uri, order: Constants.MISSION_ORDER_BASE_INT}).convertBankMission(bankMission);
var order = MissionSort.calcOrderBySortMethod(mission, MissionStore.getMissions(), GameStore.getGame().sort_method);
mission['order'] = order;
AppDataController.addMissionFromBank(mission);
},
In this function, I'm converting a stock mission (bankMission) into a regular mission and saving it to a game with the correct order key. Then I'm posting this new regular mission to my server, the callback of which is handled in my MissionStore.
Since this conversion code is important, I want to write a test for it but can't figure out how to do it since there seems to only be examples for testing stores and React components. Any suggestions?
Are you using the flux dispatcher or requiring AppDataController?
Jest will automatically mock modules that you bring in via browserify's require. If you are importing AppDataController via require, then your test might look like this:
jest.dontMock('MissionAction.js') // or path/to/MissionAction.js
describe('#addMissionFromBank', function(){
beforeEach(function(){
MissionAction.addMissionFromBank(exampleMission);
});
it('calls AppDataController.addMissionFromBank', function(){
expect(AppDataController.addMissionFromBank).toBeCalled());
});
});
you want to call the non-mocked method that youre testing and check that the mock is called. to check if its been called at all use
#toBeCalled()
or if you want to check that its called with a specific value (for instance, check that its called with whatever mission evaluates to) use
#toBeCalledWith(value)
You could mock/spyOn AppDataController, and check that it receives a correct mission object. Something like this in jasmine, I'm not sure if it is the same in jest:
spyOn(AppDataController, 'addMissionFromBank');
MissionActions.addMissionFromBank(someBankMission);
expect(AppDataController.addMissionFromBank).toHaveBeenCalledWith(expectedMission);

Use protractor to test loading text

In my angularjs application, I need to get some data from remote, and during the time, I showed a "loading data ..." text on the page.
The page is basically like this:
<div ng-show="remoteData">
{{remoteData}}
</div>
<div id="loading" ng-hide="remoteData">
Loading data, wait ...
</div>
And in the angularjs code:
function SomeCtrl($scope, $http) {
$http.get("/...").success(function(data) {
$scope.remoteData = data;
});
}
But how can I test it with protractor?
Since we use mocking files provided in local server in e2e test, the $http.get(...) part will run really fast, and when I try to check the "loading" div, it always be hidden since the data has already loaded.
If there any way to test it?
In my application, I decided to implement the following scheme:
backend, when run in development mode, adds an additional middleware which is looking at the value of the cookie header
if the cookie header named __km_delay is present, it contains a number with the delay set in ms. This middleware will sleep for the given amount of time before handling control to the next middleware
At the same time, I use the following with Protractor:
browser.manage().addCookie('__km_delay', millis, '/')
And when I'm done with the test, I invoke:
browser.manage().deleteCookie('__km_delay')
This has the advantage of not having to modify the Angular app logic in any way for testing.
If you're wondering why I chose cookie and not another HTTP header, is because I found no way of setting an extra header with Protractor. And cookie is a header anyway ;)
BTW, I use similar thing to test 5xx server responses.

How to intercept AngularJS $http logging for display in page

I want to intercept console log message from AngularJS and display them in a div on the page. I need this in order to debug ajax traffic in a PhoneGap app.
This is an example of the kind of errors I want to capture:
I tried this Showing console errors and alerts in a div inside the page but that does not intercept Angular error messages.
I also tried the solution gameover suggested in the answers. No luck with that either. Apparently $http is handling error logging differently.
I guess the answer you tried has the right idea but you're overriding the wrong methods. Reading here I can see angularJs uses $log instead of console.log, so to intercept you can try to override those.
Something like this:
$scope.$log = {
error: function(msg){document.getElementById("logger").innerHTML(msg)},
info: function(msg){document.getElementById("logger").innerHTML(msg)},
log: function(msg){document.getElementById("logger").innerHTML(msg)},
warn: function(msg){document.getElementById("logger").innerHTML(msg)}
}
Make sure to run that after importing angular.js.
EDIT
Second guess, override the consoleLog method on the LogProvider inner class on angular.js file:
function consoleLog(type) {
var output ="";
//arguments array, you'll need to change this accordingly if you want to
//log arrays, objects etc
forEach(arguments, function(arg) {
output+= arg +" ";
});
document.getElementById("logger").innerHTML(output);
}
I've used log4javascript for this purpose. I create the log object via
var log = log4javascript.getLogger('myApp')
log.addAppender(new log4javascript.InPageAppender());
I then use this in a value dependency, and hook into it where needed (e.g. http interceptor).
A more lightweight approach might be to use $rootScope.emit and then have a component on your main page which prepends these log messages to a visible div, but this will require you to change all your calls to console.log (or redefine the function in your js).
I think that this message is not even displayed from AngularJS. It looks like an exception which has not been caught in any JavaScript (angular.js just appears on top of your stack because that's the actual location where the HTTP request is being sent).
Take a look at ng.$exceptionHandler. That should be the code you seem to be interested in. If not, take a quick web search for „JavaScript onerror“ which should tell you how to watch for these kinds of errors.
I would rather user an $http interceptor.
Inside the responseError function, you can set a property on a service that will be exposed to the div's scope.

Resources