Testing modules dependent on other modules, and providers and services in them - possible. Let's say we need to test module 'A' that depends on module 'B':
totally not coffeescript, rather pseudo code for brevity :)
beforeEach ->
angular.module('B',[])
module ($provide)->
$provide.provider 'foo', ->
this.$get = -> # ...etc...etc.
it 'testing module A', ->
module('A')
inject -> # ...etc...etc.
Now, imagine we have quite a few modules that need to be mocked, and many services and providers. So how would you move faking 'B' part into a separate file?
and how they'd be used after? It's quite possible to use module loaders (webpack, browserify) but how would one do without? Using angular capabilities and what Karma offers out of the box?
Is it possible to wrap entire mock into an angular module and load it in beforeEach?
Yes, actually, making services their own modules can easily be tested in Angular using Karma.
As long as you have your testing framework set up properly—Angular mocks, and all of the required modules are loaded into Karma (check the Karma config)—then this is fairly straight-forward.
The below example assumes FirstService is the service you are testing, and SecondService is another service that FirstService depends on and interacts with.
describe('FirstService', function () {
var SecondService;
beforeEach(module('FirstService'));
beforeEach(inject(function (_SecondService_) {
SecondService = _SecondService_;
spyOn(SecondService, 'doSomethingElse');
}));
it('calls SecondService.doSomethingElse', function () {
FirstService.doSomething();
expect(SecondService.doSomethingElse).toHaveBeenCalled();
});
});
You include the module in a beforeEach as specified on the first line—do not use square brackets [] when including it as that redefines the module and you want to include the existing module as-is.
Although you should omit the square brackets when importing modules, you should be sure that when you are initially creating your FirstService module (e.g., in your app.js file, if that's how your project is set up) that you are indeed including SecondService as a dependency:
angular.module('FirstService', ['SecondService'])
The second beforeEach is injecting the SecondService module. It is already globally available to be injected and can be accessed by its module name with a leading and trailing underscore, i.e. _SecondService_. I've assigned it to a variable so that it is available to be used in the it statements, otherwise I would need to inject it in each it statement instead of a beforeEach.
I am creating a spy using Jasmine's spyOn to spy on the method doSomethingElse to ensure it has been called. The second parameter of spyOn (the method name) should have quotes but the first should not—it should be the variable you just defined for your SecondService.
The example unit test tests that calling FirstService.doSomething() calls SecondService.doSomethingElse().
Related
In my app I need to inject "dateFilter" in the config block. I know I can't do it like this:
.config(function(dateFilter){})
Since dateFilter is not a provider or a constant, it's not available during config.
However, after some research, I made it work by using the following in the config:
angular.injector(["ng"]).get('dateFilter')('2014-01-01','yyyy/MM/dd');
Doesn't this mean that I can get anything during config? Then what's the point making only providers and constants injectable during config? Is it bad to do something like angular.injector(["ng"]).get('dateFilter') during config?
angular.injector shouldn't be used in production, unless the circumstances are really exotic (i.e. almost never). It creates a new injector instance and introduces some overhead. Conventional Angular DI is good for its testability, while angular.injector turns a part of the application into untestable piece of code. Always reuse current injector inside the app, if possible (i.e. almost always).
Usually 'how to use service instance in config block' type of questions indicates an XY problem. The fact that Angular uses config to configure service providers that thereafter will create service instances (chicken-egg dilemma) suggests that the application should be refactored to respect Angular life cycle.
However, built-in filters are stateless helper functions, and their use in config phase is relatively harmless. dateFilter service is defined by $filterProvider, and $filterProvider should be injected to get to dateFilterProvider. The problem is that dateFilter depends on $locale service, which wasn't instantiated yet. $locale is constant (in broad sense) that doesn't depend on other services, so it has to be instantiated too.
angular.module('...', [
'ngLocale' // if used, should be loaded in this module
])
.config(($filterProvider, $localeProvider, $provide, $injector) => {
var $locale = $injector.invoke($localeProvider.$get);
var dateFilterProvider = $injector.get('dateFilterProvider')
var dateFilter = $injector.invoke(dateFilterProvider.$get, { $locale: $locale });
$provide.constant('dateHelper', dateFilter);
})
This is a hack should be taken into account in tests (dateHelper service should superficially tested) but is relatively trouble-free and idiomatic.
you cant inject services in config only provides but you can do it in app.run here's the calling order:
app.config() //only provides can be injected
app.run() //services can be injected
directive's compile functions (if they are found in the dom)
app.controller()
directive's link functions (again, if found)
I'm trying to compose some unit tests in Karma/Jasmine for a particular module in my project, destination-filters.
Module Decleration:
angular.module('destination-filter', ['ngSanitize']);
My tests fail unless I remove ngSanitize as a dependency. To my understanding that is because when the module is instantiated it will try and pull in that dependency but because in my spec.js file I haven't declared that module it is failing.
Spec File:
describe('Destination Filter Controller', function () {
// Set the variables
var $controller;
var mockNgSanitize;
beforeEach(module('destination-filter'));
beforeEach(function() {
module(function($provide) {
$provide.value('ngSanitize', mockNgSanitize);
});
});
beforeEach(inject(function (_$controller_) {
$controller = _$controller_('DestinationFilterController');
}));
it('should expect the controller to not be null', function() {
// Check the controller is set
expect($controller).not.toBeNull();
});
});
Previously, when mocking out services or functions, the $provide method has proven very useful but I'm not sure my use of it is correct here. I'm assuming $provide used in this way can't mock entire modules but rather services?
To clarify, if I remove the ...['ngSantize'])... from my module deceleration the tests instantiate correctly. The error I am receiving with it left in is Error: [$injector:modulerr] destination-filter
There are three options you could take for using ngSanitize in your tests:
inject the service into your test
stub a method call on ngSanitize
mock the entire ngSanitize service
The option you choose is really dependent on the use of ngSanitize in your working code (not your test code).
Whichever one you go for you need to make the service available in your test, there is no need for $provider (this covers option 1 and there is no need to do any more than this if you just want to make this available to your filter):
beforeEach(module('ngSanitize'));
beforeEach(inject(function(_ngSanitize_) { // the underscores are needed
mockNgSanitize = _ngSanitize_;
}));
Also, make sure that all js files are picked up and loaded by karma. You can define this in karma.conf.js by adding them to the files: property.
2. Stub a method on the service
I like stubs and find them very useful when writing tests. Your tests should only test one thing, in your case a filter. Stubs give you more control over your tests and allow you to isolate the thing under test.
Typically filters, controllers, anything call on lots of other things (services or factories like $http or ngSanitize).
Assuming that your filter is using ngSanitize's $sanitize to sanitize some html you could stub out that method to return sanitized html you have defined to test against your expectations:
// in a beforeEach
spyOn(mockNgSanitize, "$sanitize").and.returnValue('<some>sanitized<html>');
mockNgSanitized.$sanitize(yourDirtyHtml);
See the jasmine docs for more info.
You might have to play around with spying on the right service but this should work ok.
3. Mock the entire service
I don't think you want to go with this option because it will drive you insane figuring out what needs mocking plus mocks can create unrealistic expectations and also, not particularly useful for your use case. If you really want to have a go then something like the below is heading in the right direction (again see the jasmine docs)
beforeEach(function() {
mockNgSanitize = ('ngSanitize', ['linky', '$sanitize', '$sanitizeProvider'];
});
it('mocks the ngSanitize service', function() {
expect(mockNgSanitize.linky).toBeDefined();
});
NB: in all the code above make sure you continue to declare any variables up at the top of your describe block.
angular
.module('password_forgot', ['app.auth'])
.controller('password_forgot', main);
main.$inject = ['auth'];
function main(auth) {
auth.sendEmail().then(function(){
//blablabla
});
}
Should I inject MyFactories 3 times? What is the best practice for doing this?
You're confusing three very different things:
a module: that's where various components (controllers, services, directives, filters) are registered. Modules can depend on other modules. In your example, you're defining a module named 'password_forgot' which depends on a module named 'MyFactories'. Your application is a module that depends on other modules, that depend on other modules, etc. The application is thus the union of all the components registered in all those modules. Note that factories are not angular components. A module named 'MyFactories' should probably rather be named 'MyServices'.
a service: that's an angular component that can be injected in other angular components. In your example, you're injecting a service named 'MyFactories' into the controller 'main'. It's very unusual, and probably an error, to name a service the same way as you name a module. You shouldn't do that. The name 'MyFactories' is a pretty bad name for a service. A service should have a specific responsibility, like 'translate', or 'authentication' or 'products'
a factory: a factory is a function that is registered into a module under a given name ('authentication', for example), and whose responsibility is to create and return the unique instance of the service of that name ('authentication'). This function is called once by angular, and the returned object or function is the service that is injected in the other components.
The line
main.$inject = ['MyFactories', '$scope'];
is only necessary if you minify your JS code. If you plan to do that, then I advise you to avoid inserting such a line of code by yourself, and to rely on ng-annotate, before the minification, to modify your code in order for it to be minifiable.
Please note this is not a duplicate of:How to inject module and make it accesible to entrie angular app
I have a module (app.config) that I would like to inject into my entire app.
The module needs to be accessible within all other modules and submodules(modules inside modules) injected into myApp
For example, my app looks like this:
angular.module('myApp', [
'app.config',
'module#1',
'module#2',
'module#3',
'module#4'
])
.config...
/////////////////////////////////
Here's app.config
angular.module('app.config', []).
constant('NAME1', 'Name1').
constant('NAME2', 'Name2');
////////////////////
I want 'app.config' injected in such a way that it should be accesible in module#500 which is not directly a dependecy of 'myApp', but a dependecy of module#1 - which, in turn, is a dependecy of myApp.
module#1 is defined as such (as shown,module#500 is a dependecy of module#1):
angular.module('module#1', [
'module#500',
'module#501',
'module#502',
...
]);
Here's my problem:
angular.module('module#500', []).
service('serviceOne', serviceOne);
function ServiceOne($http, NAME1) {
var service = {
getMyProfile: function(){return $http.get('api/' + NAME1);}
};
return service;
}
Problem - I get an error-> Uncaught Error: [$injector:unpr] Unknown provider: NAME1Provider <-NAME1 <- serviceOne But I thought I injected it to the entire app???
I don't want to add module#500 directly as dependency to 'myApp' I wan't to leave module#500 as a dependecy of module#1. And I want to leave module#1 as a dependency of myApp
I don't want to individually inject app.config to each and every module either. Any other solutions?
I don't want to individually inject app.config to each and every
module either. Any other solutions?
I don't know what solution you could be expecting? With this line: angular.module('module#500', []). how is module#500 going to know about anything else, it has nothing given to it. Maybe I'm misunderstanding something here.
edit, as I've just read your post and comments from yesterday: I think you're not understanding dependency-injection properly. It only goes one way, module#1 has access to module#500, but module#500 doesn't have access to module#1. It has to be that way: How could you unit-test module#500 if it has some behavior that depends on module#1, which is not mentioned anywhere in its code?
I'm guessing your project doesn't really call for so many modules, if they all depend on the same config variables. Those factories and services that depend on the config variables should be in the same module with it. That is proper compartmentalization and will make your life easier in the long run.
If you don't want to make myApp dependent on app.config (though it would be the right thing to do because its submodules depend on it), you can load config module with manual bootstrapping
angular.bootstrap(document, ['myApp', 'app.config']);
instead of ng-app="myApp".
You forgot to inject NAME1 in your ServiceOne service.
eg: function ServiceOne($http, NAME1)
ngMock does some magic to automatically include itself if you include angular-mocks.js in your index.html.
What's the simplest way to force angular to load a module in test mode simply by including a file and not having to edit any module dependencies.
The only way to load a module is by calling angular.module(...). ngMocks loads "itself" by calling:
angular.module('ngMock', ['ng']).provider(...).config(...);
You don't need to declare a module as a dependency to load it. You can just include angular.module('<moduleName>', [<moduleDependencies>...]); in its script.
If you mean "how is ngMock automagically added to the dependency list of any module loaded using window.module or angular.mock.module, it is because ngMocks creates a custom injector, such that it takes the list of dependencies and prepends 'ngMock':
window.inject = angular.mock.inject = function() {
...
return isSpecRunning() ? workFn() : workFn;
...
function workFn() {
var modules = currentSpec.$modules || [];
modules.unshift('ngMock'); // <-- this line does the trick
modules.unshift('ng');
...
You could create your own function that prepends your module in the list of dependencies before instantiating, but I hardly believe this will help in testing. On the contrary, it will be one more source of errors (and will result in possibly "hiding" dependency errors).