Minified $provider injection with jasmine and angular - javascript

We have a project (angular) and some unittests for it (jasmine+sinon), which when minified creates some issues. For the actual code, we've solved these problems by injecting using the staticly typed string array, e.g. ['locationService', 'etcService'].
Unfortunately for the unittests, the minification has some more problems to solve. As an example:
module(function($provide){
$provide.service('etc..',...);
}
Code above immediately becomes unusuable since the provider variable gets renamed to something like 'a'. I've tried to tweak it a bit wrapping the function with something like below:
function injectTest($provide){
// do the same stuff
}
injectTest.$inject = ['$provide'];
which was a recommended solution in some other online posts. The problem is with modules this really doesn't work. I've tried both:
module(angular.injector().invoke(injectTest)); // which results in 'Unknown provider: $provideProvider <- $provide
and
module(injectTest); // which results in 'Unknown provider: nProvider <- n'
Is there any way to inject the $provider into a module without breaking on minification?

Inline injection :
var myFN = ['$provide', function($provide){
// do stuff
}]
Now if you want to bind a function to a 3rd party library where you need service let's say in my sample your function need the service CRUDService and receive a params objects from the 3rd party :
var myFN = ['CRUDService', function(CRUDService){
// do some init stuff
// you can either make it a singleton by sotrng the function and return the reference or either return new function on each call
return function(params){
// do stuff
};
}] ;
// now to bind it to your 3rd party
objectFor3rdParty = {fn:$injector.invoke(myFN)};
I use only inline injection instead of $inject, matter of taste i guess.

Related

Difference between javascript modularisation and dependency Injection

What 's the difference with the modularisation of a javascript code (with browserify by example) and the dependency injection?
Are they synonymes? Are the two going together? Or Am I missing some point?
Modularisation refers to breaking code into individual, independent "packages".
Dependency injection refers to not hardcoding references to other modules.
As a practical example, you can write modules which are not using dependency injection:
import { Foo } from 'foo';
export function Bar() {
return Foo.baz();
}
Here you have two modules, but this module imports a specific other hardcoded module.
The same module written using dependency injection:
export function Bar(foo) {
return foo.baz();
}
Then somebody else can use this as:
import { Foo } from 'foo';
import { Bar } from 'bar';
Bar(Foo());
You inject the Foo dependency at call time, instead of hardcoding the dependency.
You can refer this article:
Modules are code fragments that implement certain functionality and
are written by using specific techniques. There is no out-of-the box
modularization scheme in the JavaScript language. The upcoming
ECMAScript 6 specification tends to resolve this by introducing the
module concept in the JavaScript language itself. This is the future.
and Dependency injection in JavaScript
The goal
Let's say that we have two modules. The first one is a service which
makes Ajax requests and the second one is a router.
var service = function() {
return { name: 'Service' };
}
var router = function() {
return { name: 'Router' };
}
We have another function which needs these modules.
var doSomething = function(other) {
var s = service();
var r = router();
};
And to make the things a little bit more interesting the function
needs to accept one more parameter. Sure, we could use the above code,
but that's not really flexible. What if we want to use ServiceXML or
ServiceJSON. Or what if we want to mockup some of the modules for
testing purposes. We can't just edit the body of the function. The
first thing which we all come up with is to pass the dependencies as
parameters to the function. I.e.:
var doSomething = function(service, router, other) {
var s = service();
var r = router();
};
By doing this we are passing the exact implementation of the module
which we want. However this brings a new problem. Imagine if we have
doSomething all over our code. What will happen if we need a third
dependency. We can't edit all the function's calls. So, we need an
instrument which will do that for us. That's what dependency injectors
are trying to solve. Let's write down few goals which we want to
achieve:
we should be able to register dependencies
the injector should accept a function and should return a function which somehow gets the needed resources
we should not write a lot, we need short and nice syntax
the injector should keep the scope of the passed function
the passed function should be able to accept custom arguments, not only the described dependencies
A nice list isn't it. Let's dive in.

Isolation with Angular on karma jasmine

I have a set of tests that are spread across 2 modules.
The first module has tests and for it's dependencies i declare mocks to test it without any influence from it's dependent module, like so:
beforeEach(function(){
angular.mock.module(function ($provide) {
$provide.value("mockServiceFromModule1", mockServiceFromModule1);
$provide.value("mockServiceFromModule2", mockServiceFromModule2);
});
angular.module('module1', []);
angular.module('module2', []);
angular.mock.module('moduleThatIAmTesting');
angular.mock.inject(function (_$rootScope_, _$q_, _$httpBackend_, ..., somethingFromTestModule) {
});
})
The second module has a series of tests and all of them pass when i run only them.
beforeEach(function(){
angular.mock.module('module1');
angular.mock.inject(function (_$rootScope_, _$q_, _$httpBackend_, ..., somethingFromModule1) {
});
})
Both tests when run with f(Running only them) works, but when i run the whole test suit i get errors, specially regarding module declaration or $httpBackend.
How can i make jasmine run each test as if they were the only tests?
It seems i am messing with the angular/modules/$httpBackEnd on each test and the changes are being propagated when it starts a new test.
Update 1
I have a jsFiddle representing the issue .
The structure of the problem is :
Some test is ran with a mock dependant module
Later another test wants to test the actual mocked module
Since the first moldule was already loaded we can't overwritte it and the test fails.
On the JSFiddle the error about $httpBackend without nothing to flush is because the request for the expectedGet is never hit, and it's never hit because of the previously loaded empty module
It's important to notice that the ORDER of the tests is the only thing relevant to failing as in this JSFiddle with the same tests they pass.
I could of course make a tests order and bypass this but i am aiming to find a way to do the tests with isolation without worrying about other tests side effects.
The problem you are experiencing is due to the nature of how the $compileProvider handles a new directive being registered with a pre-existing name.
In short; You are not overriding your old directive, you are creating a secondary one with the same name. As such, the original implementation runs and tries to grab baz.html and $httpBackend throws as you have not set up an expectation for that call.
See this updated fiddle that did two changes from your original fiddle.
Do not inject the parentModule to your childModule spec. That line is not needed and it is part of the reason you are seeing these errors. Oh and angular.module is evil in the land of tests. Try to not use it.
Decorate the original directive if you wish to roll with the same name as the original one, or name it something else. I've opted for naming it something else in the fiddle, but I have supplied code at the end of my answer to show the decorator way.
Here's a screenshot of what happens in the following scenario:
Module A registers a directive called baz.
Module B depends on module A.
Module B registers a directive called baz.
As you can probably imagine, in order for the module system to not insta-gib itself by letting people overwrite eachothers directives - the $compileProvider will simply register another directive with the same name. And both will run.
Take this ng-click example or this article outlining how we can leverage multiple directives with the same name.
See the attached screenshot below for what your situation looks like.
The code on lines 71 to 73 is where you could implement solution #2 that I mentioned in the start of my answer.
Decorating baz
In your beforeEach for testModule, replace the following:
$compileProvider.directive('baz', function () {
return {
restrict: 'E',
template: '{{value}}<div ng-transclude></div>',
controllerAs: 'bazController',
controller: function ($scope, fooService) {
$scope.value = 'baz' + fooService.get()
},
transclude: true
};
});
With this:
$provide.decorator('bazDirective', function ($delegate) {
var dir = $delegate[0];
dir.template = '{{value}}<div ng-transclude></div>';
dir.controller = function ($scope, fooService) {
$scope.value = 'baz' + fooService.get();
};
delete dir.templateUrl;
return $delegate;
});
jsFiddle showing the decorator approach
What about the call to angular.module('parent', [])?
You should not call angular.module('name', []) in your specs, unless you happen to be using the angular-module gist. And even then it's not doing much for you in the land of testing.
Only ever use .mock.module or window.module, as otherwise you will kill your upcoming specs that relate to the specified module, as you have effectively killed the module definition for the rest of the spec run.
Furthermore, the directive definition of baz from parentModule will automatically be available in your testModule spec due to the following:
angular.module('parent', []).directive('baz', fn());
angular.module('child', ['parent']);
// In your test:
module('child'); // Will automatically fetch the modules that 'child' depend on.
So, even if we kill the angular.module('parent', []) call in your spec, the original baz definition will still be loaded.
As such, the HTTP request flies off due to the nature of $compileProvider supporting multiple directives with the same name, and that's the reason your spec suite is failing.
Also, as a last note; You are configuring undefined modules in your beforeEach blocks. If the goal is to configure the module of your test, you are in the wrong.
The syntax is as follows:
mock.module('child', function ($compileProvider, /** etc **/) {
});
// Not this:
mock.module('child');
mock.module(function ($compileProvider, /** etc **/) {
});
This can be seen in the screenshot I posted. The $$moduleName property of your mocked baz definition is undefined, whereas I am assuming you would want that to be child.

Are JavaScript anonymous function parameter names significant?

I'm going through this Angular tutorial and have noticed that variables seem to be freely added to functions as needed. I figured up to this point that there were just conventions but am I mistaken? Consider the last file:
app.controller('PostsCtrl', function ($scope, $location, Post) {
$scope.posts = Post.all;
$scope.post = {url: 'http://'};
$scope.submitPost = function () {
Post.create($scope.post).then(function (ref) {
$location.path('/posts/' + ref.name());
});
};
Here "$location" was added to function() between $scope and Post. Is $location the only option for the 2-nd parameter in an anonymous function here with 3 parameters or is angular somehow looking at the name of the 2nd parameter and deducing that it needs to inject $location there? Where in the documentation can I see all the conventions for 1, 2, 3, etc parameter versions of this function?
This code doesn't appear to work btw. Post is undefined.
With angular, the names are significant; in plain javascript, not really.
With that, however, if you wanted them to be insignificant in the example above, you could do:
app.controller('PostsCtrl', ['$scope','$location', 'Post',
function (foo, bar, foobar) {
....
}
]);
In which case you're mapping the first, second, and third parameters to $scope, $location, and Post respectively. This is actually a better way to do this, as when you minify with angular, it is going to change the names of those parameters, and it is going to break it.
In this case, you could order those parameters however you'd like. Angular uses Dependency Injection to supply dependencies to your controller. In this case, it will infer those dependencies based on the names so you can order them however you'd like.
Unfortunately, using this method with minification is impossible. In that case, you need to specify your dependencies. Your controller function must then take them in the order that they are defined:
app.controller('PostsCtrl',
['$scope','$location','Post', function($scope, $location, Post) {
}]);
Ahh I was confused by this at first as well. Angular has a nice feature called Dependency Injection. The names do matter because angular looks at the names of the parameters and decides what object to pass to the function. In this rare case in JavaScript, names matter but order does not.
Since you already have other answers with Angular specific instructions, I'll try to explain in simple code how Angular achieves dependency injection in JavaScript and how one might go about doing something similar in plain JS.
First it turns the function into a string, then reads the parameters, and extracts the property from the dependency container, and finally calls the function with the parameters that were extracted. Here's a very simple example of how one might do this:
// parses function and extracts params
function di(app, f) {
var args = f.toString()
.match(/function\s+\w+\s*?\((.*?)\)/)[1] // weak regex...
.split(/\s*,\s*/)
.map(function(x){return app[x.replace(/^\$/,'')]})
return function() {
return f.apply(this, args)
}
}
// Example
// A dependency container
var app = {
foo: 'this is foo',
baz: 'this is baz'
}
// this is like adding a function with the Angular ecosystem
// the order of arguments doesn't matter
var test = di(app, function test($foo, $baz) {
console.log($foo) //=> this is foo
console.log($baz) //=> this is baz
})
// No need to pass arguments, they were injected
test()
//$ this is foo
//$ this is baz
But handling DI by stringifying the function has drawbacks; old browsers don't support Function.prototype.toString very well, and minifiers will shorten the variable names making this technique useless. But Angular can get around this problem by injecting strings and parameters that match those strings; minifiers won't touch the strings.
Obviously AngularJS does much more than this, but hopefully it will clear your mind on "how the hell is this possible?!" -- Well, it's kind of a hack.
The answer here is really Angular specific. You want to read about Dependency Injection here:
https://docs.angularjs.org/guide/di
The Implicit Dependencies section should be good reading. In short, the name does matter for Angular but the order does not. This is NOT the case for raw JavaScript however.
From the Angular site:
There are three equivalent ways of annotating your code with service name information:
Implicitly from the function parameter names
Using the $inject property annotation
Using the inline array annotation

Why do I get the error: Unknown provider: personHubProvider <- personHub

I am trying to work things out with angularjs, dependency injections, factories and signalR hubs, but I get the error Unknown provider: personHubProvider <- personHub and I cannot figure out what is causing it.
My code is like this:
First I declare the module:
var ucp = angular.module('UCP', []);
which is used in the html tag by ng-app="UCP".
Then I do some configuration on the ucp module for the hub settings and creating a signalRHub factory to get the hubs without typing much of the same code:
ucp.config(function($routeProvider) {
//Declaring routes, removed this part of code cause I think it hasn't to do
//with the problem I got
$.support.cors = true;
$.connection.hub.url = config.signalR.connectionURL;
$.connection.hub.logging = config.signalR.logging;
$.connection.hub.start();
}).factory('signalRHub', [function() {
return {
person: $.connection.Persons.server
};
}]);
Then, I create another factory, that takes care of getting the data from the server:
ucp.factory('personHub', ['signalRHub', function(signalRHub) {
return {
get: function(onsuccess, onerror) {
signalRHub.person.get()
.done(function(persons) {
onsuccess(persons);
})
.fail(function(error) {
onerror(error);
});
}
}
}]);
and this factory I inject in my controller so I can execute the call to get the data from the server and put it in the scope which provides to show the data in the browser:
ucp.controller('personController', ['$scope', 'personHub', function($scope, personHub){
var self = this;
$scope.init = function() {
personHub.get(self.ongetsuccess, self.ongeterror);
}
self.ongetsuccess = function(persons) {
$scope.persons = persons;
};
self.ongeterror = function(error) {
};
}]);
When I open the webpage I get the error I mentioned before: Error: Unknown provider: personHubProvider <- personHub.
I think something goes wrong with creating the personHub factory service, which on his turn causes an Dependency Injection error for the controller. My question is, what is causing the error, am I doing something wrong with creating the personHub factory and if so, what am I doing wrong?
As I had stated in the comments I know this error occurs because the injected $provider isn't defined therefore it's reporting that there is nothing to provide said $provider. A $provider might be a factory, service, controller, or value (there's probably others I'm forgetting) and must be defined before it is referenced for an injection.
More on providers and injection here: https://github.com/angular/angular.js/wiki/Understanding-Dependency-Injection
The way I've been dealing with organizing code is by encapsulating most of the parts necessary for a given view into one js file. In that JS file I start off with a services section where I define a new module like:
angular.module("loginModule.services",[/*dependencies*/]).service("loginService", [/*dependencies*/function(){ return {get:function(){return "what up!"}};}]);
Then lower in the file I define my controllers like
angular.module("loginModule.controllers",[/*dependencies*/]).controller("LoginCtrl" ,["$scope", function($scope) { /* code here* /}]);
then at the bottom of the file
angular.module("loginModule", ["loginModule.services", "loginModule.controllers"]);
And finally in my mainApp.js (with the main ng-app module)
angular.module("mainApp", ["loginModule"]);
I've defined my script blocks in the head at the moment.
<script type="text/javascript" src="components/loginModule.js"></script>
<script type="text/javascript" src="mainApp.js"></script>
Also to note my loginModule actually depends on other services for which the javascript files come afterward. This doesn't seem to be an issue since the mainApp.js is being deferred until the end.
I just checked some of this in Chrome and it appears the JS files from my computer do load in order and as pairs (only two per domain at a time then when a response comes back the next request is fired). I also tried moving the script blocks from the head to the bottom of the HTML and I can't tell any difference (my laptop has an SSD and mostly local files so probably more appreciable in a production scenario).

Reset closure variables for unit testing in Javascript

I have the following module in Javascript
var module = (function (){
var cache = {};
return {
postMessage: function (msg){
if(!cache[msg]){
cache[msg] = true;
console.log(msg);
}
}
}
}());
When I write unit tests in Jasmine for this module I would like to have a clean module with an empty cache variable at the beginning of each test. I can't find a solution for this problem except for a helper function that clears the cache and that is publicly available.
Is there a way to clear closure variables for unit testing? I don't need a solution for production code, because in case I would like to clear in production I would have such a helper function anyways.
There is no way to reset the cache in your case. What you have here is a Singleton with a private state, so your lost, cause you can't reset the state and you cant create a new instance of module in your test.

Categories

Resources