AngularJS: Automatically inject dependencies for tests - angularjs

I have a lot of tests in my angular app (tested with Karma & Jasmine in Coffee Script), and I always end up writing all the dependencies of my modules like this:
myModule = dependency1 = dependency2 = dependency3 = undefined
beforeEach inject (_myModule_, _dependency1_, _dependency2_, _dependency3_) ->
myModule = _myModule_
dependency1 = _dependency1_
dependency2 = _dependency2_
dependency3 = _dependency3_
In the long term, this becomes really annoying, because I have to specify all the dependencies of a module again in my tests. And furthermore, when something changes, I have to add the dependency again to that place, so that I can use it in my test.
My question is, if there is any solution, to inject a modules dependencies automatically in my tests, and assign them to the window object, to use them in my tests?

Related

AngularJS - how to write a unit test containing the momentjs library?

I am writing an app with AngularJS 1.5.3.
I want to test one of my services and I want to use momentjs in the tests.
test example:
it('should have the correct number of weeks in the calendar month for Feb', function () {
var dateString = '2018-02-01';
Calendar.data.currentCalendarMonth = moment(dateString).startOf('month');
Calendar.buildMonth();
var start = moment(dateString).startOf('month').isoWeek();
var end = moment(dateString).endOf('month').isoWeek();
expect(end - start + 1).toBe(Object.keys(Calendar.data.weeks).length);
});
When I run the tests I get this error:
TypeError: moment(...).year(...).week is not a function
I have tried to import momentjs in my karma config file but it had no effect.
Edit: Moment is listed as a dependency in my bower config file. I tried to put the moment files before my files in karma config but it didn't help the result.
moment(...).year(...).week is used in my Calendar service but not in my unit test there.

Mock an AngularJS module to pass to another

I'm having some trouble mocking a factory belonging to one of my modules. The factory I would like to mock has 2 dependencies:
Factory class:
angular.module('serviceapp')
.factory('claims.service', ['applicationSettings', 'localStorageService', function (applicationSettings, localStorageService) {
//Factory code here
}]);
Test class:
//Instantiate some values
var mockAppSettings = {};
var mockStorageService = {};
var $factory; //Will hold my factory
//Targeting my module for mocking
beforeEach(angular.mock.module('serviceapp'));
//Providing some values for the dependencies of my module
beforeEach(module('serviceapp', function ($provide) {
$provide.value('applicationSettings', mockAppSettings);
$provide.value('localStorageService', mockStorageService);
}));
//Problems start here
beforeEach(inject(function ($injector) {
$factory = $injector.get('claims.service');
}));
I get an error message
Failed to instantiate module serviceapp due to:
Failed to instantiate module accountModule due to:
Module 'accountModule' is not available!
When investigating I see that accountModule is listed as a dependency for the serviceApp module.
App.module class:
angular.module('serviceapp', [accountModule])
However I'm having some trouble mocking this module to pass to serviceapp. I have tried to mock the accountModule in the same way I have mocked the serviceapp in the beginning however this is still bring up the same error message. How can I mock and pass one module to another?
angular.mock.module('serviceapp') shouldn't be read literally. It doesn't mock a module. It is the same thing as module('serviceapp') and is used in modular environments where module is reserved.
So, all that
beforeEach(angular.mock.module('serviceapp'));
beforeEach(module('serviceapp', ...));
does is loading serviceapp twice (doesn't hurt but doesn't help either).
To avoid Module 'accountModule' is not available!, it should be (re)defined:
beforeAll(() => {
angular.module('accountModule', [])
});
The problem with this approach is that even if it was defined, it will be overridden to the end of test run. If real accountModule needs to be used in other tests, this won't be possible.
The appropriate solution for similar design issues (this also applies to dependencies that aren't desirable in tests, e.g. router modules) is
angular.module('serviceapp', ['accountModule']);
angular.module('serviceapp.internal', [])
.factory('claims.service',...);
Here serviceapp serves as a shallow wrapper for serviceapp.internal, while the latter can be safely tested. If serviceapp is top-level module that is used for bootstrapping, this indicates that the application wasn't modularized enough, this hurts testing.

Jasmine Grunt, Backbone, Require, Routes etc

i have a question. I have a full app, a lot of view(backbone), models, everything using require, routes etc. When my app is on production i have a single file main.js, like this:
<script data-main="main-gdfgfda.js" src="url/require.js"></script>
But i cant run tasks on my spec.
MY views work like this:
define(function(require){
"use strict";
var $ = require('jquery'),
Backbone = require('backbone'),
ContentFactory = require('views/ContentFactory'),
imagesLoaded = require('imagesloaded'),
ShareView = require('views/ui/ShareView');
var myView = Backbone.View.extend(....
});
And isnted i have some variables in php files.
And i cant running tests in this framework, there is a lot of error because depedency and et, what should i do, what is the best way to acess myView to start testing using jasmine?
If i put in jasmine template it will work? I cant just run SRC folder/*js, because there is a lot of dependecys and etc.
What should i do? Thx

How do I provide re-usable sample data values to my angularjs / jasmine unit-tests

I would like to provide simple constant values such as names, emails, etc to use in my jasmine unit tests.
A similar question was asked here: AngularJS + Karma: reuse a mock service when unit testing directives or controllers
In c# I would create a static class to hold little snippets of mock data. I can then use these values throughout my unit tests, like this:
static class SampleData
{
public const string Guid = "0e3ae555-9fc7-4b89-9ea4-a8b63097c50a";
public const string Password = "3Qr}b7_N$yZ6";
public const string InvalidGuid = "[invalid-guid]";
public const string InvalidPassword = "[invalid-password]";
}
I would like to have the same convenience when testing my AngularJS app using Karma / Jasmine.
I know that I can define a constant object against my angular app, I already do this for constants I use in the real code, like this:
myApp.constant('config', {apiUrl:'http://localhost:8082'})
I could add another constant just like this but only containing sample data values for use in my unit tests, like this:
myApp.constant('sampleData', {email: 'sample#email.com'})
I could then just inject the mock constant object into my tests and off I go, like this
describe 'A sample unit test', ->
beforeEach -> module 'myApp'
beforeEach inject ($injector) ->
#sampleData = $injector.get 'sampleData'
email = #sampleData.email
# etc ...
However this seems a bit fishy to me. I don't want my production code to contain sample data that is only required by my unit-tests.
How would you conveniently provide your angular / jasmine unit tests with re-usable sample data values?
Thanks
There are two ways of doing this:
spy on function calls and return fake values.
create mock classes (and possibly mock data to initialise them) and load them wherever you need
The first one is alright when you only have to fake a few calls. doing that for a whole class is unsustainable.
For example, let's say you have a service that builds some special URLs. If one of the methods depends on absUrl, you can fake it by spying on the method in the $location object:
describe('example') function () {
beforeEach(inject(function () {
spyOn($location, 'absUrl').andCallFake(function (p) {
return 'http://localhost/app/index.html#/chickenurl';
});
}));
it('should return the url http://www.chicken.org') ... {
// starting situation
// run the function
// asserts
}
Now let's say that you have a Settings Service that encapsulates data like language, theme, special paths, background color, typeface... that is initialised using a remote call to a server.
Testing services that depend on Settings will be painful. You have to mock a big component with spyOn every time. If you have 10 services... you don't want to copypaste the spyOn functions in all of them.
ServiceA uses Settings service:
describe('ServiceA', function () {
var settings, serviceA;
beforeEach(module('myapp.mocks.settings')); // mock settings
beforeEach(module('myapp.services.serviceA')); // load the service being tested
beforeEach(inject(function (_serviceA_, _settings_) {
serviceA = _serviceA_;
settings = _settings_;
}));
container
for this test suite, all calls to the Settings service will be handled by the mock, which has the same interface as the real one, but returns dummy values.
Notice that you can load this service anywhere.
(If, by any reason, you needed to use the real implementation, you can load the real implementation before the mock and use spyOn for that particular case to delegate the call to the real implementation.)
Normally you'll place the mocks module outside of the app folder. I have a test folder with the unit tests, e2e tests and a lib folder with the angular-mocks.js file. I place my mocks there too.
Tell karma the files you need for the tests:
files: [
'app/lib/jquery/jquery-1.9.1.js',
'test/lib/jasmine-jquery.js',
'app/lib/angular/angular.js',
'app/lib/angular/angular-*.js',
'test/lib/angular/angular-mocks.js',
'test/lib/myapp/*.js', /* this is mine */
'app/js/**/*.js',
'test/unit/**/*.js'
],
The file tests/lib/myapp.mocks.settings.js looks just like any other module:
(function () {
"use strict";
var m = angular.module('myapp.mocks.settings', []);
m.service('settings', function () { ... })
})
Second problem (optional): you want to change quickly the dummy values. In the example, the settings service fetches an object from the server when it is instantiated for the first time. then, the service has getters for all fields. This is kind of a proxy to the server: instead of sending a request everytime you need a value, fetch a bunch of them and save them locally. In my application, settings don't change in the server in run-time.
Something like:
1. fetch http://example.org/api/settings
2. var localSettings = fetchedSettings;
3 getFieldA: function() { return localSettings.fieldA; }
Go and pollute the global namespace. I created a file settings.data.js with a content like:
var SETTINGS_RESPONSE = { fieldA: 42 };
the mock service uses this global variable in the factory to instantiate localSettings

How can I test an AngularJS service from the console?

I have a service like:
angular.module('app').factory('ExampleService', function(){
this.f1 = function(world){
return 'Hello '+world;
}
return this;
})
I would like to test it from the JavaScript console and call the function f1() of the service.
How can I do that?
TLDR: In one line the command you are looking for:
angular.element(document.body).injector().get('serviceName')
Deep dive
AngularJS uses Dependency Injection (DI) to inject services/factories into your components,directives and other services. So what you need to do to get a service is to get the injector of AngularJS first (the injector is responsible for wiring up all the dependencies and providing them to components).
To get the injector of your app you need to grab it from an element that angular is handling. For example if your app is registered on the body element you call injector = angular.element(document.body).injector()
From the retrieved injector you can then get whatever service you like with injector.get('ServiceName')
More information on that in this answer: Can't retrieve the injector from angular
And even more here: Call AngularJS from legacy code
Another useful trick to get the $scope of a particular element.
Select the element with the DOM inspection tool of your developer tools and then run the following line ($0 is always the selected element):
angular.element($0).scope()
First of all, a modified version of your service.
a )
var app = angular.module('app',[]);
app.factory('ExampleService',function(){
return {
f1 : function(world){
return 'Hello' + world;
}
};
});
This returns an object, nothing to new here.
Now the way to get this from the console is
b )
var $inj = angular.injector(['app']);
var serv = $inj.get('ExampleService');
serv.f1("World");
c )
One of the things you were doing there earlier was to assume that the app.factory returns you the function itself or a new'ed version of it. Which is not the case. In order to get a constructor you would either have to do
app.factory('ExampleService',function(){
return function(){
this.f1 = function(world){
return 'Hello' + world;
}
};
});
This returns an ExampleService constructor which you will next have to do a 'new' on.
Or alternatively,
app.service('ExampleService',function(){
this.f1 = function(world){
return 'Hello' + world;
};
});
This returns new ExampleService() on injection.
#JustGoscha's answer is spot on, but that's a lot to type when I want access, so I added this to the bottom of my app.js. Then all I have to type is x = getSrv('$http') to get the http service.
// #if DEBUG
function getSrv(name, element) {
element = element || '*[ng-app]';
return angular.element(element).injector().get(name);
}
// #endif
It adds it to the global scope but only in debug mode. I put it inside the #if DEBUG so that I don't end up with it in the production code. I use this method to remove debug code from prouduction builds.
Angularjs Dependency Injection framework is responsible for injecting the dependancies of you app module to your controllers. This is possible through its injector.
You need to first identify the ng-app and get the associated injector.
The below query works to find your ng-app in the DOM and retrieve the injector.
angular.element('*[ng-app]').injector()
In chrome, however, you can point to target ng-app as shown below. and use the $0 hack and issue angular.element($0).injector()
Once you have the injector, get any dependency injected service as below
injector = angular.element($0).injector();
injector.get('$mdToast');

Resources