Cant unit test application with manual bootstrap in AngularJS - javascript

my code needs to do manual bootstrap cause requires loading files before laoding application.
var bootstrapModule = angular.module('bootstrapModule', []);
// the bootstrapper service loads the config and bootstraps the specified app
bootstrapModule.factory('bootstrapper', function ($http, $log, $q, $timeout) {
return {
bootstrap: function (appName) {
var deferred = $q.defer();
$http.get('config/urls/development.json')
.success(function (data) {
.....
deferred.resolve();
})
.error(function (data) {
...
});
return deferred.promise;
}
};
});
// create a div which is used as the root of the bootstrap app
var appContainer = document.createElement('div');
// in run() function you can now use the bootstrapper service and shutdown the bootstrapping app after initialization of your actual app
bootstrapModule.run(function (bootstrapper) {
bootstrapper.bootstrap('angular3App').then(function () {
// removing the container will destroy the bootstrap app
if (appContainer) appContainer.remove();
});
});
// make sure the DOM is fully loaded before bootstrapping
angular.element(document).ready(function () {
angular.bootstrap(appContainer, ['bootstrapModule']);
});
I want my unit tests to replicate this behaviour so it can have the info in the loaded file.
I am trying to bootstrap my app in beforeEach but it is not working.
beforeEach(function(done) {
var bootstrapModule = module('bootstrapModule')
// create a div which is used as the root of the bootstrap app
var appContainer = document.createElement('div');
// in run() function you can now use the bootstrapper service and shutdown the bootstrapping app after initialization of your actual app
bootstrapModule.run(function (bootstrapper) {
bootstrapper.bootstrap('angular3App').then(function () {
// removing the container will destroy the bootstrap app
appContainer.remove();
});
});
// make sure the DOM is fully loaded before bootstrapping
angular.element(document).ready(function () {
angular.bootstrap(appContainer, ['bootstrapModule']);
});
});
It is not bootstrapping accordingly. The information in my scope that is dependent from load of the bootstrapped loaded file is not available.
Anyone has any ideas?

This is an old question, but I just dealt with this issue and it seems that at least one other person has recently had a similar issue. I was banging my head against the wall to get my unit tests to run with a setup my like #saulovenancio. The factory was a way I found to import the config.json file needed for the project and it worked - it just didn't play nice with our tests.
As far as I could tell, the tests were trying to run before the promise in the bootstrapMOodule.factory was resolved. Luckily, while loading the config JSON was a requirement - loading it via service was not a project requirement - so I scrapped the service that was fetching the JSON.
I pretty much followed the approach outlined here:
How to configure your AngularJS application using environment variables
In the index.html document I added a script tag for an env.js file before the file that loads my angular app.
<html>
...
<script src="js/jquery.js"></script>
<!-- Gets the config.json -->
<script src="env.js"></script>
<!-- The App -->
<script src="js/main.js"></script>
</html>
The env.js file contains the code that loads the config.json onto the window. Mine looks something like this:
(function (window) {
window.__env = window.__env || {};
$.get( 'config.json', function( data ){
window.__env = data;
})
.error( function handleErrorResponse( xhr, status, error ){
console.error( "An AJAX error occured: " + status + "\nError: " + error ) ;
});
}(this));
From here I just loaded the window.__env into my main.js file and used it for my constants.
var env = {};
if(window){
Object.assign(env, window.__env);
}
...
angular.module('app').constant('AppSettings', env);
Once I made those changes everything worked like a charm ... big thanks to Jurgen Van de Moere. Hopefully, this will be helpful to someone.

Related

How to unit test an AngularJS service / factory

I'm already some time in the development using AngularJS, and what I do write works, but know I've come to a point where I would like to run unit tests on my AngularJS code.
I have created a very simple service that will inject a stylesheet onto the page,
see the example code below:
var OfficeSuiteStylesheetInjectorModule = angular.module('OfficeSuiteStylesheetInjectorModule', []);
OfficeSuiteStylesheetInjectorModule.factory('stylesheetInjectorService', ['$q', function($q) {
// Returns the service itself.
return {
inject: function(id, uri) {
var deferred = $q.defer();
// Embed the stylesheet into the page, but only when it's non-existing.
if (!angular.element('#' + id).length) {
var link = StylesheetFactory.Create(id, uri);
{
link.onload = deferred.resolve;
angular.element('head').append(link);
}
return deferred.promise;
}
}
}
}]);
It's not a big service, it's just dependend on $q for promises so that I can run additional logic when the stylesheet has been embedded in the page.
Now, I'm using Jasmine (I'm quite new to this) for testing my JavaScript code and I would like to test this module.
I have a skeleton:
// Tests for the angular 'StylesheetInjectorService'.
describe('StylesheetInjectorService', function() {
var stylesheetInjectorService = {};
// This code is executed before every test.
beforeEach(function() {
// Tells jamine that the module we're working on is the 'OfficeSuiteStylesheetInjectorModule'.
angular.module('OfficeSuiteStylesheetInjectorModule');
});
// Ensures that it injects a stylesheet element into the page.
it('Should inject a stylesheet element into the page.', function() {
// How to test here that the stylesheet is injected?
});
});
});
How can I inject te service in the page and ensures that the stylesheet is loaded?
Edit: Loading service now works:
beforeEach(module('OfficeSuiteStylesheetInjectorModule'));
// This code is executed before every test.
beforeEach(function() {
// Inject the required dependencies into the page.
inject(function($injector) {
stylesheetInjectorService = $injector.get('stylesheetInjectorService');
});
});
The same question is still open however. How to test if a stylesheet was embedded in the page?
Any help is highly appreciated.
Kind regards
To write a spec for the attachment of a stylesheet to angular.element('head') I would change the logic a bit to attach it to $document.head.
If you dont want to do that, I would recommend that you change your service into a directive seeing as how injecting a script element, is manipulating the DOM. That way you would kill two birds with one stone, as you would need to inject $compile to test your directive (which would enable you to $compile a custom head element to boot). But this is slightly "over the top" for now.
Implementation:
if (!angular.element('#' + id).length) {
var link = StylesheetFactory.Create(id, uri);
link.onload = deferred.resolve;
$document.head.append(link);
return deferred.promise;
}
}
beforeEach:
/**
* Sorry, this was previously $location which was just
* such a silly mistake.
*/
var $timeout;
beforeEach(function () {
inject(function ($injector) {
$timeout = $injector.get('$timeout');
});
});
it:
it('attaches the stylesheet to $document.head', function () {
styleSheetInjectorService.inject('someId', '/path/to/some/stylesheet');
$timeout.flush(); // flush promises
expect(angular.element($document.head).lastChild[0].nodeName).to.eq('LINK');
});
Something along those lines should get you up and running. Bare in mind that the spec I wrote uses the chai#expect style assertions, and the mocha test framework. Edit the syntax to fit Jasmine if you mean to copy-paste.
I had this doubt a while ago.
To inject your controllers and services into your tests you need to use a tool called Angular Mocks. Here's some official reference about it.
I sugest you use it together with the Karma enviroment.
Here's a great Getting Started tutorial:
https://www.airpair.com/angularjs/posts/unit-testing-angularjs-applications
This other one is for the Ionic Framework, but can still aplly to your case
Hope it can help.

Dynamically add AngularJS Script

I've got a project that's has a very rapid development cycle which causes many changes in the main app.js file. This file has the configs as well as controllers for the AngularJS app that's being used as part of the project. This created Caching issues which I tried solving as follows:
<script type="text/javascript">
var s = document.createElement('script')
s.setAttribute('src', 'assets/js/app-v2.js?v='+(new Date().getMilliseconds()));
document.body.appendChild(s);
</script>
However, this gives me an error in angular saying:
Failed to instantiate module appName due to:
Error: [$injector:nomod] Module 'appName' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.
My HTML is setup as
<html ng-app="appName">....</html>
So I tried setting the ng-app dynamically after loading the script but that doesn't work either. Gives me the same issue as earlier. Is it possible for me to add the appName dynamically during or after the load of the app-v2.js file?
I have found this answer from the stackoverflow while I was facing same issue.
bootstrap() will call the AngularJS compiler for you, just like ng-app.
// Make module Foo and store $controllerProvider in a global
var controllerProvider = null;
angular.module('Foo', [], function($controllerProvider) {
controllerProvider = $controllerProvider;
});
// Bootstrap Foo
angular.bootstrap($('body'), ['Foo']);
// .. time passes ..
// Load javascript file with Ctrl controller
angular.module('Foo').controller('Ctrl', function($scope, $rootScope) {
$scope.msg = "It works! rootScope is " + $rootScope.$id +
", should be " + $('body').scope().$id;
});
// Load html file with content that uses Ctrl controller
$('<div id="ctrl" ng-controller="Ctrl" ng-bind="msg">').appendTo('body');
// Register Ctrl controller manually
// If you can reference the controller function directly, just run:
// $controllerProvider.register(controllerName, controllerFunction);
// Note: I haven't found a way to get $controllerProvider at this stage
// so I keep a reference from when I ran my module config
function registerController(moduleName, controllerName) {
// Here I cannot get the controller function directly so I
// need to loop through the module's _invokeQueue to get it
var queue = angular.module(moduleName)._invokeQueue;
for(var i=0;i<queue.length;i++) {
var call = queue[i];
if(call[0] == "$controllerProvider" &&
call[1] == "register" &&
call[2][0] == controllerName) {
controllerProvider.register(controllerName, call[2][1]);
}
}
}
registerController("Foo", "Ctrl");
// compile the new element
$('body').injector().invoke(function($compile, $rootScope) {
$compile($('#ctrl'))($rootScope);
$rootScope.$apply();
});
Hope it will help you

AngularJs Protractor: Using ngMockE2E with a manually bootstrapped application

I am trying to mock data being responded to from an angularjs service using Protractor. I have been able to successfully do this in other applications by using the following technique.
Creating a mock module that is configurable via protractor.addMockModule
var httpBackendMock = function() {
angular.module('mockBackend', ['ngMockE2E', 'MyApp'])
.value('configData', arguments[0])
.run(function($httpBackend,configData) {
console.log('bootstrapped!');
$httpBackend.whenPOST(/.*/).respond(configData.postHeader, configData.postResponse);
});
};
module.exports.httpBackendMock = httpBackendMock;
Then creating a protractor test that looks like this
describe('Mocking an httpbackend', function() {
var sampleData = require('./sampleData/blog/normalLanding.json');
var configObject = require('./utils/backendConfig.json');//passed To addMockModule to be accessed via arguments[0]
var MyMockedBackend = require('./utils/httpBackendMock.js');
var ptor = protractor.getInstance();
configObject.postHeader = 200;
configObject.postResponse = sampleData;
it('Should mock a backnd', function(){
ptor.addMockModule('mockBackend', MyMockBackend.httpBackendMock, configObject);
ptor.get('/blog');
});
});
This worked perfectly for an app that was
The only angular application running on the page
Was bootstrapped via ng-app
However when working with an app that is manually bootstrapped, the app calls and returns the results from the actual service, rather than the desired mocked response. Also, no errors are thrown. For example's sake, I am trying to respond to all POST requests with the same data.
I have protractors rootElement in my configuration file set to the tag the app bootstraps on.
Does anything look particulary wrong with this settup/approach? Or could it be an issue with the order in which protractor executes in relation to calling addMockModule with a manually bootstrapped application?
Here is a link to a github repo which contains a working example of this with the Angular Phonecat Tutorial from the Angular website. The examples are in test/e2e/utils and scenerios.js
Github example
Cheers!

How do you load a javascript file into a scope

I'm trying to figure out how to load the contents of a JavaScript file into a scope. The reason for this is I want to be able to have plug and play capability and be able to load different functionality into my app. Perhaps the user role determines what functionality is loaded, or perhaps I want to tailor my app for a given client.
Here's an example of the contents of one of my pluggable JavaScript file:
myApp.LoginViewModel = (function ()
{
//DATA
var self = this;
return {
HelloWorld: function ()
{
alert('Hellow World from Login View Model');
}
};
})();
Here is an example of the configuration file listing what JS files should be loaded:
var myApp = myApp || {};
myApp.ViewModelRegistry =
{
BaseViewModel: { JavaScriptPath: "/Scripts/ViewModels/BaseViewModel.js"},
LoginViewModel:{ JavaScriptPath: "/Scripts/ViewModels/LoginViewModel.js"}
};
Here's my code that loops through ViewModelRegistry and uses jQuery to load the script:
InitializeApp: function ()
{
//REGISTER VIEW MODELS
for (var viewModelName in myApp.ViewModelRegistry)
{
var registeredModel = myApp.ViewModelRegistry[viewModelName];
$.getScript(registeredModel.JavaScriptPath, function (data, textStatus, jqxhr)
{
});
}
console.log(myApp);
}
};
My goal is to load the JavaScript function from the JS file into the myApp scope. Each pluggable JS file is scoped to myApp. I thought simply using jQuery's getScript to load the JS file would automatically update the myApp scope since all pluggable JS files are designed to that scope. However, the console.log(myApp) doesn't reveal the code from the JS files. What am I missing? Is this even feasible?
I believe your console.log is executed before any of your JS files have been retrieved and parsed. Other than that, you seem to be on the correct course. This is definitely feasible, and the terminology used to describe what you want to achieve is called Asynchronous Module Definitions.

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).

Categories

Resources