Angular Tests always fail on $logProvider when getting constants in tests - angularjs

Very new to Angular testing... using 1.3.0.rc0. To get started I'm trying to do something simple: get the value of a constant I set. Within a config.js, I have the following:
(function () {
'use strict';
var app = angular.module('app');
// create app configuration
var appConfig = {
version = '0.0.1.0',
debugMode = true
};
app.constant('config', appConfig);
app.config([function ($logProvider, config) {
// set the debugging setting of the app > same setting for the app
if ($logProvider.debugEnabled) {
$logProvider.debugEnabled(config.debugMode);
}
}]);
})();
I'm tried numerous things to write my tests (using jasmine & karma), but I keep getting an error that:
Error: [$injector:modulerr] Failed to instantiate module app due to:
TypeError: 'undefined' is not an object (evaluating '$logProvider.debugEnabled').
I get that this was a bug a while ago in the angular-mocks.js file but has since been resolved. Regardless, no matter the test I write, it doesn't work. Here's what i'm working with now, knowing that there are issues with it.
'use strict';
describe('config.js', function () {
var logProvider;
beforeEach(module(inject(function ($log) {
logProvider = $log;
})));
beforeEach(module('app', logProvider));
it('should set the config constant to the app global configuration settings', function () {
var $injector = angular.injector(['ng', 'app']);
var settings = $injector.get('config');
//var settings = inject(config);
expect(settings.debugMode).toBe(true);
});
});
Am I doing this right? If so, is there no way to get around the test issue with $logProvider?

There is much to learn about how modules work in Angular, especially under testing with ngMocks. I'll try to be brief.
One always begins by calling module (from ngMocks) one (or more times) to build up the module "cookbook" for a test run.
In any of these module calls you have an opportunity to access and stash away a previously defined provider.
The first time you call inject (from ngMocks) in a given test path, the module "cookbook" is "baked" for that path and the injector is populated based on recipes in that "cookbook".
Subsequent calls to module are irrelevant. Your expression beforeEach(module('app', logProvider)); executes too late (even if it did what you wanted, which it would not).
In fact, I'm surprised that you didn't get the error: "Error: Injector already created, can not register a module!".
inject always returns the thing created by the provider, never the provider itself. Your first beforeEach ...
beforeEach(module(inject(function ($log) {
logProvider = $log;
})));
... actually sets logProvider to the $log service, not the $logProvider.
Does this help?
Here is a sample from my forthcoming course on Ng testing that shows how to access a provider (in this case, the $logProvider). It was inspired by your question.
First, the config2 constant (I already had a value called config:
// my sample application module definition is called 'basics'
var basics = angular.module('basics', []);
/* define 'config2' constant - which is available in Ng's config phase */
basics.constant('config2', {
debugMode: true
});
// use constant in config phase
basics.config(function ($logProvider, config2) {
$logProvider.debugEnabled(config2.debugMode);
})
Now the spec (using Mocha and Chai):
describe('Basics - constant:', function() {
'use strict';
beforeEach(module('basics'));
// other stuff
describe("the $logProvider", function(){
var configConstant;
var $log;
var $logProvider;
beforeEach(module(
// Could combine with module('basics') definition in outer describe
// but only need it here in this describe
// This module definition function has access to any previously defined provider
// which in this case is any provider defined in ng, ngMocks, or basics
function( _$logProvider_) {
$logProvider = _$logProvider_;
}
));
// inject triggers injector creation; module definition now "baked"
beforeEach(inject(function(config2, _$log_){
configConstant = config2;
$log = _$log_;
}));
it("is accessible via the module function", function(){
expect($logProvider).to.exist;
});
it("is not the same as the log service", function(){
expect($logProvider).not.to.equal($log);
});
it("has same debugEnabled value as config2.debugMode", function(){
expect($logProvider.debugEnabled()).to.equal(configConstant.debugMode);
});
});
});

Related

Why anglar.mock.module does not $provide values like it $provides constants

I've looked at the documentation for angular.mock.module and a couple of examples of others using it but I seem to be running into an issue in my use-case that I don't understand.
I'm running Jasmine (2.4.1) tests with angular (1.4.9) and I have my angular app separated into multiple modules. When I attempt to mock out certain parts of my app for unit testing I want to mock out entire modules (or providers) so that I only expose the pieces I use.
Here is a very simple app that has a main module plunker which depends on plunker.service. plunker.service depends on plunker.constant.
var app = angular.module('plunker', ['plunker.service']);
app.controller('MainCtrl', function($scope, valueService, appService) {
$scope.init = function() {
$scope.appValue = valueService.getValue();
$scope.appIsRunning = appService.getStatus();
};
});
angular.module('plunker.service', ['plunker.constant'])
.service('appService', function(appSettings) {
var vm = this;
vm.getStatus = function () {
if (appSettings.isRunning) {
return true;
} else {
return false;
}
};
})
.service('valueService', function(valueSettings) {
var vm = this;
vm.getValue = function () {
return valueSettings.value;
}
});
angular.module('plunker.constant', [])
.constant('appSettings', { isRunning: true })
.constant('valueSettings', { value: 10 });
In my Jasmine tests I have a beforeEach() that registers my modules using module (aka angular.mock.module).
I have seen 3 ways of using module
string
function with $provide
object
You can see below that I use the module('plunker') (string) to register my main module and I have 3 ways of mocking out my appSettings constant (A, B, C). You will notice that the function with $provide.constant works fine but function with $provide.value does not and object does not.
beforeEach(function() {
module('plunker');
function useFunction(typeofProvider) {
module(function($provide) {
$provide[typeofProvider]('appSettings', { isRunning: false });
});
}
function useObject() {
module({
appSettings: { isRunning: false }
});
}
// A. THIS WORKS! //
useFunction('constant');
// B. THIS DOES NOT //
// useFunction('value');
// C. THIS ALSO DOES NOT!! //
// useObject();
inject(function($rootScope, $controller) {
$scope = $rootScope.$new();
ctrl = $controller('MainCtrl', {
$scope: $scope
});
});
});
I have also seen people use the following syntax...
beforeEach(function() {
var mockService = function () {
var mockValue = 10;
this.value = mockValue;
};
// D.
module('a.module.name', function newProviders($provide){
$provide.service('realService', mockService);
});
});
My questions
In my test code, why does A. work but B. and C. do not?
Is D. equivalent to calling module('a.module.name'); followed by module(function newProviders($provide) { ... });? Does placing both in the same module() call have any special effects on how things are registered or is it just a shorthand? (based on the documentation it should be a shorthand)
Related to Jasmine, specifically, do all beforeEach() calls run in the same top-to-bottom order with every execution?
Here is my plunker for the above app and jasmine code
Thanks
This happens because of how Angular injector works. In fact, there are two different injectors in Angular. The one (available as $injector in config blocks) deals with service providers. Another one (available as $injector anywhere else) deals with service instances. Providers and instances are cached and stored internally.
$provide.constant('service') creates both provider and instance of name 'service' at call time.
All other types of services are lazily instantiated. They create 'serviceProvider' provider at call time, but 'service' instance is created on the first injection.
Since Angular service instance is a singleton, it refers to instance cache before the instantiation. If the instance is in the cache, it is reused and not instantiated. constant service instance is eagerly instantiated, so only another constant can override the instance.
Object properties in angular.mock.module are shortcuts for $provide.value, and useObject() equals to useFunction('value') in this example.
As long as module order stays the same,
module('a.module.name', function ($provide) { ... });
is indeed a shortcut for
module('a.module.name');
module(function ($provide) { ... });
Due to the fact that appSettings object isn't used in config blocks (the primary use of constant service), it is more convenient to make it value.

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.

AngularJS + Jasmine: How to start from fresh module for each spec in a test in suite

Here's my simple code
var app = angular.module('myApp', [])
.value('SimpleValue', {
aaa: '£££'
});
and unit tests
describe('ttt', function() {
beforeEach(module('myApp'));
it('ttt', inject(function(SimpleValue) {
expect(SimpleValue.aaa).toEqual('£££');
SimpleValue.aaa = 4;
}));
it('ttt', inject(function(SimpleValue) { // This doesn't start fresh and fails
expect(SimpleValue.aaa).toEqual('£££');
}));
});
describe('ttt', function() { // Neither does this
var SimpleValue;
beforeEach(module('myApp'));
beforeEach(inject(function(_SimpleValue_) {
SimpleValue = _SimpleValue_;
}));
it('ttt', function() {
expect(SimpleValue.aaa).toEqual('£££');
});
});
That is not the behaviour I was expecting from beforeEach(module('myApp'));. What is the point of using module('myApp') before each spec when according to documentation: https://docs.angularjs.org/api/ngMock/function/angular.mock.module it merely
collects the configuration information which will be used when the injector is created by inject.
? I thought that it would reset myApp to start clean for each it block but it turns out it doesn't.
Can anyone shed some light on this? Please see a plunk:
http://plnkr.co/edit/xMmstHxL2prQukXpXYXm?p=preview
Services registered with .value don't get reset. I can't tell you why, but it does seem unexpected.
Register using .factory for example, and tests pass as expected.
var app = angular.module('myApp', [])
// .value('SimpleValue', {aaa: '£££'});
.factory('SimpleValue', function () {
return {aaa: '£££'};
});
The thing here is that in your first test you are modifying properties of an object. But it is important to realize that there is only one copy of this object in the whole system. Re-writing myApp slightly differently should make it more clear:
var simpleValue = {aaa: '£££'};
var app = angular.module('myApp', []).value('SimpleValue', simpleValue);
See, you were kind of assuming that this part of the script is executed as part of each spec, which is not the case (this fragment of the code is loaded into browser only once).
Hopefully this plunker makes the whole story clear: http://plnkr.co/edit/iLuIPaLyVFgzoTLygFz1?p=preview

Injecting mock angular service dependencies

I have a service, 'Inputs', defined in module 'Puts', that depends on a second service, 'InputCreator'. I need to stub the InputCreator service in order to test the Inputs service.
As I understand the answer here, I should create a module containing my stub service, then create a new 'Test' module, specifying the module under test and then the stub module as dependencies. And then pull the service from the injector. Like so:
beforeEach(function() {
angular.module.('Puts'); // contains the service 'Inputs'
angular.module('Mocks',[])
.service('InputCreator',function(){
var mockInputs = {
//stubbed behaviour goes here
};
return mockInputs;
});
});
angular.module('Test',['Puts', 'Mocks'];
inject(function($injector){
Inputs = $injector.get('Inputs');
});
});
However, the injector function responds with 'unknown InputsProvider <- Inputs'.
Where have I gone astray?
Thanks!
Having figured this out, I thought I'd answer my own question. The big mistake above was using angular.module rather than angular.mock.module, that is convenience referenced as module by angular-mock. They aren't the same thing at all!
Additionally, it's enough to initialize the mock service with angular.mock.module, so long as you do it before you initialize the module under test. There's no need for this 'wrapping the modules in a third module' business as suggested in the question linked above. To wit:
describe("Test Service", function() {
var TestService, getvaluestub;
beforeEach(function() {
// create mock service
var mock = {getvalue:function(){}}
angular.module('dependencymodule',[])
.service('dependencyservice',function () {
return mock;
});
//mock the function we are stubbing, (that, in this case, returns value 4)
getvaluestub = sinon.stub(mock,'getvalue')returns(4);
//instantiate your mock service
module('dependencymodule');
//instantiate the module of the service under test,
//that depends on 'dependencyservice' mocked above
//(ie - testmodule includes the service 'testservice')
module('testmodule');
//inject your test service for testing
inject(function ($injector) {
TestService = $injector.get('testservice');
})
//tests go here.....
If the dependency module already exists, you could either still do all of the above, or you could acquire the service from the $injector, insert your spies and stubs, and >then< instantiate the service under test. It's important that the spies/stubs are set up >before< the dependent service is instantiated, or it will be instantiated without them. It looks like this:
describe("Test Service", function() {
var TestService, DependencyService, getvaluestub;
beforeEach(function() {
// these modules are specified in the application
module('dependencymodule');
module('testmodule');
inject(function ($injector) {
DependencyService = $injector.get('testservice');
getvaluestub = sinon.stub(DependencyService,'getvalue').returns(4);
OtherService = $injector.get('otherservice');
})
});
// test go here
So, there you go. Hopefully this is useful to someone who searches for 'Injecting mocks into angular services'.

Manually bootstrapping AngularJS and then getting the module

Generally, I'd do the following and there would be an ng-app in my HTML:
var myApp = angular.module("myApp", []);
myApp.controller("AttributeCtrl", function ($scope) {
$scope.master = {
name: "some name"
};
});
However, I need to manually bootstrap angular because I'm only using it in a part of my app that is loaded via jQuery's $.load(). If I do the following:
main.js - this is where the page I want to use angular on is being pulled in
$("#form").load(contextPath + url, params, function() {
angular.bootstrap($("#angularApp"));
});
And then the page being pulled in has it's own javascript:
function AttributeCtrl($scope) {
$scope.master = { name: "some name"};
}
This works, however, ideally, I'd like my controllers to be scoped at the module level. So I modified the above code like so
main.js
$("#form").load(contextPath + url, params, function() {
angular.bootstrap($("#angularApp", ["myApp"]));
});
and then...
var app = angular.module("myApp"); // retrieve a module
app.controller("AttributeCtrl", function($scope) {
$scope.master = { name: "some name"};
});
Retrieving the module this way doesn't seem to work, though. Am I doing something wrong?
You cannot create a controller after you've bootstrapped the app. See the documentation for angular.bootstrap.
You should call angular.bootstrap() after you've loaded or defined your modules. You cannot add controllers, services, directives, etc after an application bootstraps.
I don't know if this is just in the example code you have here but:
angular.bootstrap($("#angularApp", ["myApp"]));
should be
angular.bootstrap($("#angularApp"), ["myApp"]);
Your code for retrieving the module should work.
Updated
They updated the documentation and now it reads like this
Each item in the array should be the name of a predefined module or a
(DI annotated) function that will be invoked by the injector as a run
block. See: {#link angular.module modules}
It seems a bug.
The way you implemented to retrieve the module is correct. Just quote it from the doc to make it clear since it may not be well-known.
When passed two or more arguments, a new module is created. If passed
only one argument, an existing module (the name passed as the first
argument to module) is retrieved.
For the problem you mentioned, long story short...
The bootstrap function calls createInjector with the module list ['ng', ['ngLocale', function(){...}] , 'myApp'] (the last one is the module you passed in)
function bootstrap(element, modules) {
...
var injector = createInjector(modules);
Inside createInjector(), it calls loadModules for each module passed in
function createInjector(modulesToLoad) {
forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); });
And loadModules calls angularModule, which is initialized as angularModule = setupModuleLoader(window);, which creates the object window.angular.module
function loadModules(modulesToLoad){
....
var moduleFn = angularModule(module); // triggers the error
The the error occurs, since angularModule takes 2nd parameter as requires. Without it, it will throws an exception on this line (line 1148) throw Error('No module: ' + name);
Reported: https://github.com/angular/angular.js/issues/3692
Not sure if this counts as a bug or an implementation decision (albeit a seemingly poor one). Adding an empty array solves the undefined require problem that you were having and should solve your problem overall.
var app = angular.module("myApp", []); // create a module
app.controller("AttributeCtrl", function($scope) {
$scope.master = { name: "some name"};
});`
Also, in your fiddle you call {{name}} which won't render. You should be calling {{master.name}}
Edit
Thank you all for the downvotes .. Here's a working example. Good luck!
http://plnkr.co/edit/UowJpWYc1UDryLLlC3Be?p=preview

Resources