To mock a provider - angularjs

I have a module that in its config uses a provider that I need to mock.
provider itself has register function that takes 2 arguments and $get function that returns an object, but that's not really important (I think) since I'd be mocking it anyway. Just in case here's the entire provider
module uses the provider like this:
angular.module('replenishment').config(configureReplenishmentColumnDef);
configureReplenishmentColumnDef.$inject = ['columnDefProvider'];
function configureReplenishmentColumnDef(columnDefProvider) {
let config = {
matchWhen: ((schema)=> _.get(schema, 'format') === 'replenishment'),
$get: replenishmentColDef
};
columnDefProvider.register(config);
}
replenishmentColDef.$inject = ['$q', 'schema', 'medData'];
function replenishmentColDef($q, schema, medData) {
....
}
I started putting together a spec like this (our tests written in CoffeeScript)
describe 'replenishment-module', ->
columnDefProvider = undefined
beforeEach ->
module ($provide)->
$provide.provider 'columnDef', ->
#.register = sinon.spy()
#.$get = sinon.spy()
return // had to put an explicit return here, as #DTing suggested
module 'replenishment'
inject ->
Now I don't know how to properly mock provider's methods. Can you guys please show me, maybe I need to use stubs instead of spies.

Try adding explict returns like this, I think you are getting a malformed function:
describe 'replenishment-module', ->
columnDefProvider = undefined
beforeEach ->
module ($provide)->
$provide.provider 'columnDef', ->
#.register = sinon.spy()
#.$get = sinon.spy()
return
module 'replenishment'
inject ->
I think this is what your javascript equivalent looks like:
beforeEach(function() {
module(function($provide) {
return $provide.provider('columnDef', function() {
this.register = sinon.spy();
return this.$get = sinon.spy();
});
});
which would make it so your Provider columnDef doesn't have a $get factory method:
function Something() {
return this.get = function() {
console.log("get");
};
}
var something = new Something();
something.get();

Related

AngularJS - how to use factory inside a provider

I wonder how can i use factory inside a provider for using it inside config.
Since i understand config can be injected only with providers - i wonder how can i achieve the following functionality
app.provider('getUserLanguageProvider',['$injector', function($injector) {
this.$get = function(getUserLang) { // calling a factory
var userLang = getUserLang.getLang()
return {
getLang: function() {
return userLang
}
}
};
}]);
app.config(['$translateProvider', 'getUserLanguageProvider', function ($translateProvider, getUserLanguageProvider) {
 
const lang = getUserLanguageProvider.getLang() // get the language key from provider
$translateProvider.preferredLanguage(lang); // set the language key brought by getUserLang.getLang() factory
}]);
I've tried many versions of what i've described - but none works.
Don't append Provider to the provider name. Simply use the name of the service:
̶a̶p̶p̶.̶p̶r̶o̶v̶i̶d̶e̶r̶(̶'̶g̶e̶t̶U̶s̶e̶r̶L̶a̶n̶g̶u̶a̶g̶e̶P̶r̶o̶v̶i̶d̶e̶r̶'̶,̶[̶'̶$̶i̶n̶j̶e̶c̶t̶o̶r̶'̶,̶ ̶f̶u̶n̶c̶t̶i̶o̶n̶(̶$̶i̶n̶j̶e̶c̶t̶o̶r̶)̶ ̶{̶
app.provider('getUserLanguage',['$injector', function($injector) {
this.$get = ['$http', function($http) { // calling a factory
var userLang = getUserLang.getLang()
return {
getLang: function() {
// ....
}
}
}];
}]);
The $injector service will automatically append the Provider suffix to the configuration object. The configuration object will then be injectable into .config functions as the name of the service appended with Provider as a suffix.

Angular.js unit test controller with others modules dependencies

Background:
I have an angular.js controller 'GetResultController' with some external dependencies:
angular.module('data-service',[]).service('DataService', DataService);
angular.module('GetResultModule', ['datatables', 'data-service']);
angular.module('GetResultModule')
.controller('GetResultController',
['$q','DTColumnBuilder','DataService', GetResultController]);
function GetResultController($q, DTColumnBuilder, DataService) {
let self = this;
self.getResult = function getResult() {
return 'positive';
}
}
Questions:
ver-1:
describe('testGetResultController', () =>{
beforeEach(angular.mock.module('datatable');
//do i need to create the mock module for 'data-service', and how to do next?
}
)
ver-2:
describe('testGetResultController', () => {
beforeEach(angular.module('datatable',[]);
beforeEach(angular.module('common-service',[]);
let c;
beforeEach(angular.mock.inject(function(_$controller_) {
c = _$controller_('GetResultController'); // is it correct to get back the controller?
}
it('test', ()=> {
expect(c).toBeDefined(); // why c is undefined?
});
})
As I want to draft a unit test to verify 'GetResultController'. How can I initialize the dependencies, mock the object to test the 'getResult' method. Do we have any experience to handle this case.
Many thanks

How to pass data between controller-Service-controller in angularjs

I am trying to bind id from controller1 to one of myservice function.But I get below error
TypeError: _this.dashboardService.chartid is not a function
Please find my code snippet
Service1.js
var Service1 = function (
$http) {
this.$http = $http;
};
Service1.prototype.chartid = function (id) {
return id;
};
appmodule.service('dashboardService', DashboardService);
controller1.js
Controller1.prototype.Chart = function (data) {
_this.service1.chartid(data.key);
};
You haven't inject your service inside your controller, in more classic way:
var controller1=angular.module('controllermodule',[]);
controller1.controller('{name of the controller}',function({service to inject}){}
Javascript prototype functions can be invoked on its objects.When you add prototype you are defining the class whose functions are available on the object.
e.g.
var Service = function() {};
Service.prototype.callMePlease = function() {
console.log('ok Calling now');
return 'Just called';
};
console.log('callMePlease is undefined on Service or is it ? Service.callMePlease='+Service.callMePlease);
var serviceInstance = new Service();
console.log('callMePlease is NOT undefined on serviceInstance or is it ? serviceInstance .callMePlease='+serviceInstance.callMePlease);
var didYouCall = serviceInstance.callMePlease();
console.log('didYouCall='+didYouCall);
Given this you actually need the instance of service1 in your case.
It is best you follow Angular injection model as per official angularjs DI documentation to allow AngularJS create instances for you.
However you may use programattic approach (not advised). In your case
Controller1.prototype.Chart = function (data) {
var myService1 = $injector.get('service1');
myService1.chartid(data.key);
};

Jasmine: Trying to test an AngularJS factory function

I am totally new to testing in AngularJS. I have setup karma, and am now attempting to test a certain function in a factory I have written.
Here is a snippet of my factory:
app.factory('helpersFactory', ['constants', function (constants) {
return {
someFunction: function() {
},
is24x24Icon: function (iconNum) {
return ((iconNum >= 10090 && iconNum <= 10125) ;
}
};
}]);
I then have this test:
describe('Factory: helpersFactory', function () {
beforeEach(module('ppMobi'));
var fct;
beforeEach(inject(function ($factory) {
fct = $factory('helpersFactory');
}));
it('should detect iconNum 10090 is a 24 x 24 icon', function () {
var iconNum = 10090;
var is24x24Icon = fct.is24x24Icon(iconNum);
expect(is24x24Icon).toBeTruthy();
});
});
I get an error from Karma telling me it cannot read 'is24x24icon' of undefined. Therefore I can only assume my factory has not been created properly during the test. I do have a dependency on constants in the factory used by other functions. This is just an angular.constant() I have setup on my main application module.
I have found some other posts, but am unsure how to proceed, do I need to inject my constants dependency into my test?
Kind of new myself but I think you need to use the underscore name underscore trick to inject your factory:
var fct;
beforeEach(inject(function (_helpersFactory_) {
fct = _helpersFactory_;
}));
This blog uses mocha but I found it useful and the Karma stuff should be the same: https://www.airpair.com/angularjs/posts/testing-angular-with-karma
And yes you will need to inject the constants as well (the link shows how) but your posted code does not seem to use constants so you won't need it for this particular test.

Can the Angular $injector be decorated with $provide.decorator?

Perhaps this is a terrible idea, but if it is then please tell me why and then pretend that it's an academic exercise that won't see the light of day in production.
I'd like to add some logic to the Angular $injector service, to monitor when certain services are injected into other services. Since it seems that Angular provides a mechanism for decorating services, I thought this would be the way to go. However, the following code throws an error.
(function () {
'use strict';
var app = angular.module('app');
app.config(['$provide', function ($provide) {
$provide.decorator('$injector', ['$log', '$delegate', addLoggingToInjector]);
}]);
function addLoggingToInjector($log, $delegate) {
var baseInstantiate = $delegate.instantiate;
var baseInvoke = $delegate.invoke;
$delegate.instantiate = function (type, locals) {
// $log.debug('Calling $injector.instantiate');
baseInstantiate(type, locals);
};
$delegate.invoke = function (fn, self, locals) {
// $log.debug('Calling $injector.invoke');
baseInvoke(fn, self, locals);
};
return $delegate;
};
})();
The specific error is:
Uncaught Error: [$injector:modulerr] Failed to instantiate module app
due to: Error: [$injector:unpr] Unknown provider: $injectorProvider
The answer is: no.
$provide.decorator is used to intercept service creation -- that is why it is called from .config block, when there is still time to configure all services, as none of them has been created. $provide.decorator basically gets the Provider of the service and swaps its $get with newly delivered decorFn.
$injector is not like other services. It is created, as the very first step of bootstrapping an application -- way before app.config is called. [look at functions: bootstrap and createInjector in angular source code]
But hey, you can achieve your goal quite easily by tweaking the source code just a bit :-) Particularly look at function invoke(fn, self, locals).
UPDATE I got some inspiration from #KayakDave. You actually do not have to dig in the source-code itself. You can use the following pattern to observe each call to any of $injector methods:
app.config(['$injector', function ($injector) {
$injector.proper =
{
get : $injector.get,
invoke : $injector.invoke,
instantiate : $injector.instantiate,
annotate : $injector.annotate,
has : $injector.has
}
function getDecorator(serviceName)
{
console.log("injector GET: ", serviceName);
return this.proper.get(serviceName);
}
function invokeDecorator(fn, self, locals)
{
console.log("injector INVOKE: ", fn, self, locals);
return this.proper.invoke(fn, self, locals);
}
function instantiateDecorator(Type, locals)
{
console.log("injector INSTANTIATE: ", Type, locals);
return this.proper.instantiate(Type, locals);
}
function annotateDecorator (fn)
{
console.log("injector ANNOTATE: ", fn);
return this.proper.annotate(fn);
}
function hasDecorator(name)
{
console.log("injector HAS: ", name);
return this.proper.has(name);
}
$injector.get = getDecorator;
$injector.invoke = invokeDecorator;
$injector.instantiate = instantiateDecorator;
$injector.annotate = annotateDecorator;
$injector.has = hasDecorator;
}]);
PLNKR
You can't use the Angular decorator service on $injector. As Artur notes $injector is a bit different from other services. But we can create our own decorator.
Why we can't use Angular's decorator
At the code level the issue is that $injector doesn't have a constructor function- there's no $injectorProvider.
For example both of these return true:
$injector.has('$location');
$injector.has('$locationProvider')
However, while this returns true:
$injector.has('$injector')
this returns false:
$injector.has('$injectorProvider')
We see the importance when we look at the Angular decorator function:
function decorator(serviceName, decorFn) {
var origProvider = providerInjector.get(serviceName + providerSuffix),
orig$get = origProvider.$get;
origProvider.$get = function() {
var origInstance = instanceInjector.invoke(orig$get, origProvider);
return instanceInjector.invoke(decorFn, null, {$delegate: origInstance});
};
}
And
providerSuffix = 'Provider'
So the Angular decorator expects to operate on the service's constructor (serviceName + providerSuffix). Pragmatically, since we don't have an $injectorProvider we can't use decorator.
Solution
What we can do is override the Angular injector's get function ourselves by replacing the injector's default get with one that calls the original, Angular defined, get followed by our function.
We'll apply this to $injector rather than the nonexistent $injectorProvider like so:
app.config(['$provide','$injector', function ($provide,$injector) {
// The function we'll add to the injector
myFunc = function () {
console.log("injector called ", arguments);
};
// Get a copy of the injector's get function
var origProvider = $injector,
origGet = origProvider.get;
//Override injector's get with our own
origProvider.get = function() {
// Call the original get function
var returnValue = origGet.apply(this, arguments);
// Call our function
myFunc.apply(this,arguments);
return returnValue;
}
}]);
You'll see the provider being injected is the first augment, so app.value('aValue', 'something'); yields the following log statement:
injector called ["aValueProvider"]
Demo fiddle

Resources